Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
commitment_key.test.cpp
Go to the documentation of this file.
1
5
6#include <gtest/gtest.h>
7
8namespace bb {
9
10template <typename Curve> class CommitmentKeyTest : public ::testing::Test {
11 public:
13 using Fr = typename Curve::ScalarField;
15 using GroupElement = typename Curve::Element;
17
19
20 // Naive MSM computation for testing
21 static Commitment commit_naive(const CK& ck, const Polynomial& poly)
22 {
23 auto srs = ck.get_monomial_points();
24 GroupElement result = srs[poly.start_index()] * poly[poly.start_index()];
25 for (size_t i = poly.start_index() + 1; i < poly.end_index(); ++i) {
26 result += srs[i] * poly[i];
27 }
28 return result.normalize();
29 }
30
32 {
33 constexpr size_t n = 16;
34 CK ck(n);
35
36 Polynomial zero_poly(n);
37 Commitment commitment = ck.commit(zero_poly);
38
39 EXPECT_TRUE(commitment.is_point_at_infinity());
40 }
41
43 {
44 constexpr size_t n = 16;
45 CK ck(n);
46
47 // Polynomial with mostly zero coefficients
48 Polynomial poly(n);
49 poly.at(3) = Fr::random_element();
50
51 Commitment commitment = ck.commit(poly);
52 Commitment expected = commit_naive(ck, poly);
53 EXPECT_EQ(expected, commitment);
54 }
55
57 {
58 constexpr size_t n = 16;
59 CK ck(n);
60
61 auto poly = Polynomial::random(n);
62 Commitment commitment = ck.commit(poly);
63 Commitment expected = commit_naive(ck, poly);
64 EXPECT_EQ(expected, commitment);
65 }
66
68 {
69 // Test various non-power-of-2 sizes
70 for (size_t n : { size_t{ 10 }, size_t{ 100 }, size_t{ 1000 }, size_t{ 1234 } }) {
71 CK ck(n);
72
73 EXPECT_EQ(ck.srs_size, n);
74 // Note: get_monomial_size() may be >= n since it returns the underlying SRS size
75 EXPECT_GE(ck.get_monomial_size(), n);
76
77 auto poly = Polynomial::random(n);
78 Commitment commitment = ck.commit(poly);
79 Commitment expected = commit_naive(ck, poly);
80 EXPECT_EQ(expected, commitment);
81 }
82 }
83
85 {
86 constexpr size_t n = 16;
87 CK ck(n);
88
89 // Create multiple polynomials
91 for (size_t i = 0; i < 5; ++i) {
92 polys.emplace_back(Polynomial::random(n));
93 }
94
95 RefVector<Polynomial> poly_refs(polys);
96 auto batch_commitments = ck.batch_commit(poly_refs);
97
98 EXPECT_EQ(batch_commitments.size(), polys.size());
99
100 // Verify batch commit matches individual commits and naive computation
101 for (size_t i = 0; i < polys.size(); ++i) {
102 Commitment individual = ck.commit(polys[i]);
103 Commitment expected = commit_naive(ck, polys[i]);
104 EXPECT_EQ(batch_commitments[i], individual);
105 EXPECT_EQ(batch_commitments[i], expected);
106 }
107 }
108
110 {
111 constexpr size_t n = 32;
112 CK ck(n);
113
114 // Create polynomial with non-zero start index
115 // Polynomial(size, virtual_size, start_index) requires start_index + size <= virtual_size
116 // Indices must be in range [start_index, start_index + size)
117 constexpr size_t start_index = 8;
118 constexpr size_t poly_size = 16;
119 constexpr size_t virtual_size = start_index + poly_size; // 24
120 Polynomial poly(poly_size, virtual_size, start_index);
121 for (size_t i = 0; i < poly_size; ++i) {
122 poly.at(start_index + i) = Fr::random_element();
123 }
124
125 Commitment commitment = ck.commit(poly);
126 Commitment expected = commit_naive(ck, poly);
127 EXPECT_EQ(expected, commitment);
128 }
129
130 // Regression test for a zero-counting bug in Pippenger's MSD radix sort
131 // (sort_point_schedule_and_count_zero_buckets in process_buckets.cpp).
132 //
133 // The bug: the recursive radix sort passed `keys` instead of `top_level_keys` when recursing,
134 // causing the zero-entry counter to be overwritten by non-zero-bucket counts when the sort
135 // uses 3+ recursion levels. The inflated count makes the MSM skip valid point contributions.
136 //
137 // When does 3-level recursion occur?
138 // - Pippenger chooses bits_per_slice via a cost model (get_optimal_log_num_buckets).
139 // - bits_per_slice > 16 pads to 24 bits -> initial_shift=16 -> 3 levels (shift 16->8->0).
140 // - For BN254 (254-bit scalars), bits_per_slice=17 at ~4.6M+ points per work unit.
141 // - Multi-threading splits MSM across cores, so each work unit is total_points/num_threads.
142 // On a 32-core machine, a single work unit reaches 4.6M at ~150M total points.
143 // - Single-threaded execution (WASM, resource-constrained environments) hits the threshold
144 // at 4.6M points directly.
145 //
146 // Polynomial design (deterministic, all coefficients non-zero):
147 // get_scalar_slice extracts bits MSB-first. With bits_per_slice=17 and 15 rounds for BN254,
148 // round 13 extracts bits [16:33) of each scalar. We choose scalar values so that round 13
149 // has the bucket distribution needed to trigger the overwrite:
150 //
151 // 100 coefficients = Fr(1) -> bits [16:33) = 0 -> bucket_index = 0
152 // 10 coefficients = Fr(2^16) -> bits [16:33) = 1 -> bucket_index = 1 [DROPPED]
153 // ~5M coefficients = Fr(2^32) -> bits [16:33) = 2^16 -> bucket_index = 65536 [OVERWRITES]
154 //
155 // Fr(1) entries must be non-zero (zero scalars are filtered before the MSM) but still
156 // land in bucket 0 for round 13. They ensure point_schedule[0] has bucket_index=0 after
157 // sorting, bypassing the post-sort safety check in sort_point_schedule_and_count_zero_buckets.
158 //
159 // The bug overwrites num_zero_entries from 100 (correct) to ~5M (count at bucket 65536).
160 // The MSM span then starts ~5M entries into the sorted schedule, skipping all 10 target
161 // entries with bucket_index=1 and silently dropping their contributions.
162 //
163 // This layout is chosen for efficiency (~1.5s) and full determinism (no random scalars).
164 // The reference commitment is computed by chunking into 1M-point sub-MSMs, each using
165 // bits_per_slice <= 15 (2-level sort, bug-free).
167 {
168 constexpr size_t n = 5000000;
169 CK ck(n);
170
171 Polynomial poly(n);
172
173 constexpr size_t num_fake_zeros = 100;
174 for (size_t i = 0; i < num_fake_zeros; ++i) {
175 poly.at(i) = Fr(1);
176 }
177
178 constexpr size_t num_targets = 10;
179 for (size_t i = num_fake_zeros; i < num_fake_zeros + num_targets; ++i) {
180 poly.at(i) = Fr(65536);
181 }
182
183 for (size_t i = num_fake_zeros + num_targets; i < n; ++i) {
184 poly.at(i) = Fr(uint256_t(1) << 32);
185 }
186
187 // Commit single-threaded to keep the full point set in one work unit
188 size_t original_concurrency = get_num_cpus();
190 Commitment actual_commitment = ck.commit(poly);
191 set_parallel_for_concurrency(original_concurrency);
192
193 // Reference: sum of chunked sub-MSMs (each chunk uses bits_per_slice <= 15, bug-free)
194 constexpr size_t chunk_size = 1UL << 20;
195 auto srs_points = ck.get_monomial_points();
196 GroupElement correct_sum;
197 correct_sum.self_set_infinity();
198
199 for (size_t offset = 0; offset < n; offset += chunk_size) {
200 size_t this_chunk = std::min(chunk_size, n - offset);
201 std::span<const Fr> chunk_coeffs(&poly[offset], this_chunk);
202 PolynomialSpan<const Fr> chunk_span(0, chunk_coeffs);
203 std::span<const Commitment> chunk_points = srs_points.subspan(offset, this_chunk);
204
205 auto chunk_result = scalar_multiplication::pippenger_unsafe<Curve>(chunk_span, chunk_points);
206 correct_sum += chunk_result;
207 }
208 Commitment correct_commitment(correct_sum);
209
210 EXPECT_EQ(actual_commitment, correct_commitment);
211 }
212};
213
214using Curves = ::testing::Types<curve::BN254, curve::Grumpkin>;
216
218{
219 TestFixture::test_commit_to_zero_poly();
220}
222{
223 TestFixture::test_commit_sparse_poly();
224}
226{
227 TestFixture::test_commit_random_poly();
228}
230{
231 TestFixture::test_non_dyadic_srs_size();
232}
234{
235 TestFixture::test_batch_commit();
236}
237TYPED_TEST(CommitmentKeyTest, CommitWithStartIndex)
238{
239 TestFixture::test_commit_with_start_index();
240}
241TYPED_TEST(CommitmentKeyTest, DISABLED_PippengerZeroCountRegression)
242{
244 GTEST_SKIP() << "BN254 only: Grumpkin CRS has insufficient points for the 5M threshold";
245 }
246#ifndef NDEBUG
247 GTEST_SKIP() << "Too slow in debug builds";
248#else
249 TestFixture::test_pippenger_zero_count_regression();
250#endif
251}
252
253} // namespace bb
CommitmentKey object over a pairing group 𝔾₁.
typename Curve::AffineElement Commitment
static Commitment commit_naive(const CK &ck, const Polynomial &poly)
typename Curve::Element GroupElement
typename Curve::ScalarField Fr
Structured polynomial class that represents the coefficients 'a' of a_0 + a_1 x .....
size_t start_index() const
static Polynomial random(size_t size, size_t start_index=0)
size_t end_index() const
Fr & at(size_t index)
Our mutable accessor, unlike operator[]. We abuse precedent a bit to differentiate at() and operator[...
A template class for a reference vector. Behaves as if std::vector<T&> was possible.
typename Group::element Element
Definition grumpkin.hpp:63
typename Group::affine_element AffineElement
Definition grumpkin.hpp:64
ssize_t offset
Definition engine.cpp:62
std::filesystem::path bb_crs_path()
void init_file_crs_factory(const std::filesystem::path &path)
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
TYPED_TEST_SUITE(CommitmentKeyTest, Curves)
size_t get_num_cpus()
Definition thread.cpp:33
TYPED_TEST(CommitmentKeyTest, CommitToZeroPoly)
CommitmentKey< Curve > ck
void set_parallel_for_concurrency(size_t num_cores)
Definition thread.cpp:23
::testing::Types< curve::BN254, curve::Grumpkin > Curves
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
static field random_element(numeric::RNG *engine=nullptr) noexcept