Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
shplemini_zk_mask_rank.test.cpp
Go to the documentation of this file.
1// Checks that tail_halving_support() produces the exact support assumed by the
2// Shplemini ZK masking argument (SHPLEMINI_ZK_MASKING.md): the tail-halving
3// layout
4// {E-1, E-2, N/2, N/2-1, N/4, N/4-1, ..., 2, 1}
5// of exactly min(2d, 2^d) distinct in-range entries, E = round_up(extent, 2).
6// The masking argument is proved over this support, so the generator must match
7// it for the zero-knowledge property to hold.
8
10
11#include <algorithm>
12#include <array>
13#include <cstddef>
14#include <gtest/gtest.h>
15#include <unordered_set>
16#include <vector>
17
18namespace bb {
19namespace {
20
21constexpr std::array<size_t, 4> kDValues = { 4, 8, 11, 12 };
22
23// Distinct, in-range, and exactly min(2d, 2^d) entries.
24TEST(ShpleminiZkMaskSupport, ShapeContract)
25{
26 for (size_t d : kDValues) {
27 const size_t n = size_t{ 1 } << d;
28 const auto support = tail_halving_support(d, n);
29 const size_t expected_size = std::min(2 * d, n);
30
31 EXPECT_EQ(support.size(), expected_size) << "support size at d=" << d;
32
33 std::unordered_set<size_t> seen(support.begin(), support.end());
34 EXPECT_EQ(seen.size(), support.size()) << "duplicate support entry at d=" << d;
35 for (size_t s : support) {
36 EXPECT_LT(s, n) << "out-of-range support entry at d=" << d;
37 }
38 }
39}
40
41// Contains the top pair {E-1, E-2} and every dyadic halving pair that fits.
42TEST(ShpleminiZkMaskSupport, ContainsTailHalvingPairs)
43{
44 for (size_t d : kDValues) {
45 const size_t n = size_t{ 1 } << d;
46 const auto support = tail_halving_support(d, n); // extent = n => E = N.
47 const std::unordered_set<size_t> seen(support.begin(), support.end());
48
49 // Top pair at E = N.
50 EXPECT_TRUE(seen.count(n - 1)) << "missing top entry N-1 at d=" << d;
51 EXPECT_TRUE(seen.count(n - 2)) << "missing top entry N-2 at d=" << d;
52
53 // Dyadic halving pairs {N/2^level, N/2^level - 1}, as far as 2d allows.
54 for (size_t level = 1; level < d; ++level) {
55 const size_t base = n >> level;
56 EXPECT_TRUE(seen.count(base)) << "missing dyadic entry " << base << " at d=" << d;
57 EXPECT_TRUE(seen.count(base - 1)) << "missing dyadic entry " << (base - 1) << " at d=" << d;
58 }
59 }
60}
61
62// At extent = N = 2^d the support is exactly the descending S proved in
63// SHPLEMINI_ZK_MASKING.md, with no tail-fill.
64TEST(ShpleminiZkMaskSupport, ExactDescendingSupportAtFullExtent)
65{
66 for (size_t d : kDValues) {
67 const size_t n = size_t{ 1 } << d;
68 std::vector<size_t> expected = { n - 1, n - 2 };
69 for (size_t level = 1; level < d; ++level) {
70 const size_t base = n >> level;
71 expected.push_back(base);
72 expected.push_back(base - 1);
73 }
74 ASSERT_EQ(expected.size(), std::min(2 * d, n)) << "expected support size at d=" << d;
75
76 EXPECT_EQ(tail_halving_support(d, n), expected) << "descending support mismatch at d=" << d;
77 }
78}
79
80// A non-dyadic extent rounds up to an even top pair and is still distinct,
81// in-range, and full size.
82TEST(ShpleminiZkMaskSupport, OddExtentRoundsUp)
83{
84 const size_t d = 8;
85 const size_t n = size_t{ 1 } << d;
86 const size_t extent = (n / 2) + 5; // odd, in the upper half.
87 const auto support = tail_halving_support(d, extent);
88 const size_t E = extent + 1; // round_up to even.
89
90 const std::unordered_set<size_t> seen(support.begin(), support.end());
91 EXPECT_EQ(support.size(), std::min(2 * d, n));
92 EXPECT_EQ(seen.size(), support.size());
93 EXPECT_TRUE(seen.count(E - 1)) << "missing rounded-up top entry E-1";
94 EXPECT_TRUE(seen.count(E - 2)) << "missing rounded-up top entry E-2";
95}
96
97// At extent E = N/2 + 2 the top entry E-2 lands on N/2, so the raw list has a
98// duplicate. The support must still come out with min(2d, N) distinct in-range
99// entries: the distinctness check fails if de-dup is dropped, the size check
100// fails if the tail-fill does not refill the dropped slot.
101TEST(ShpleminiZkMaskSupport, CollisionExtentStaysFull)
102{
103 for (size_t d : kDValues) {
104 const size_t n = size_t{ 1 } << d;
105 const size_t extent = (n / 2) + 2; // E = N/2 + 2, the only collision.
106 const auto support = tail_halving_support(d, extent);
107
108 const std::unordered_set<size_t> seen(support.begin(), support.end());
109 EXPECT_EQ(support.size(), std::min(2 * d, n)) << "size at d=" << d;
110 EXPECT_EQ(seen.size(), support.size()) << "duplicate support entry at d=" << d;
111 for (size_t s : support) {
112 EXPECT_LT(s, n) << "out-of-range support entry at d=" << d;
113 }
114 }
115}
116
117// The fallback boundary: at d = 3 the mask is dense (n nonzeros), at d = 4 it
118// switches to sparse (2d nonzeros). d = 3 is the smallest size where dense and
119// sparse differ (at d < 3, min(2d, N) == N, so both fill the whole buffer and a
120// nonzero count cannot tell them apart).
121TEST(ShpleminiZkMaskSupport, DenseMaskBelowSparseThreshold)
122{
123 // Coefficients outside [start_index, end_index) are virtually zero by construction.
124 const auto count_nonzero = [](const auto& poly) {
125 size_t nonzero = 0;
126 for (size_t i = poly.start_index(); i < poly.end_index(); ++i) {
127 if (!poly.at(i).is_zero()) {
128 ++nonzero;
129 }
130 }
131 return nonzero;
132 };
133
134 // d = 3 (< threshold): dense fills all n; the sparse mask would use only 2d = 6.
135 const size_t n3 = size_t{ 1 } << 3;
136 EXPECT_EQ(count_nonzero(build_gemini_masking_poly<fr>(3, n3, n3)), n3) << "expected dense mask at d=3";
137
138 // d >= threshold: sparse mask with min(2d, N) coefficients (dense would fill N).
139 for (size_t d : { size_t{ 4 }, size_t{ 8 } }) {
140 const size_t n = size_t{ 1 } << d;
141 const auto poly = build_gemini_masking_poly<fr>(d, /*extent=*/n, /*dyadic_size=*/n);
142 EXPECT_EQ(count_nonzero(poly), std::min(2 * d, n)) << "expected sparse mask at d=" << d;
143 }
144}
145
146} // namespace
147} // namespace bb
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
std::vector< size_t > tail_halving_support(size_t d, size_t extent)
TEST(BoomerangMegaCircuitBuilder, BasicCircuit)