Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
polynomial_arithmetic.test.cpp
Go to the documentation of this file.
8#include "polynomial.hpp"
9#include <algorithm>
10#include <array>
11#include <cstddef>
12#include <gtest/gtest.h>
13#include <limits>
14#include <span>
15#include <utility>
16#include <vector>
17
18using namespace bb;
19
23TEST(polynomials, evaluate)
24{
25 auto poly1 = Polynomial<fr>(15); // non power of 2
26 auto poly2 = Polynomial<fr>(64);
27 for (size_t i = 0; i < poly1.size(); ++i) {
28 poly1.at(i) = fr::random_element();
29 poly2.at(i) = poly1[i];
30 }
31
32 auto challenge = fr::random_element();
33 auto eval1 = poly1.evaluate(challenge);
34 auto eval2 = poly2.evaluate(challenge);
35
36 EXPECT_EQ(eval1, eval2);
37}
38
39TEST(polynomials, ifft_consistency)
40{
41 constexpr size_t n = 16;
42 auto domain = evaluation_domain(n);
43 domain.compute_lookup_table();
44
45 std::array<fr, n> coeffs;
46 std::array<fr, n> values;
47 std::array<fr, n> values_copy;
48 std::array<fr, n> recovered;
49
50 for (size_t k = 0; k < n; ++k) {
51 coeffs[k] = fr::random_element();
52 values[k] = fr::zero();
53 recovered[k] = fr::zero();
54 }
55
56 // compute values[j] = sum_k coeffs[k] * ω^{j*k}
57 for (size_t j = 0; j < n; ++j) {
58 fr acc = fr::zero();
59 for (size_t k = 0; k < n; ++k) {
60 fr w = domain.root.pow(static_cast<uint64_t>(j * k));
61 acc += coeffs[k] * w;
62 }
63 values[j] = acc;
64 values_copy[j] = values[j];
65 }
66
67 // compute ifft of values, which should recover coeffs
68 polynomial_arithmetic::ifft(values.data(), recovered.data(), domain);
69
70 for (size_t k = 0; k < n; ++k) {
71 EXPECT_EQ(recovered[k], coeffs[k]); // check that ifft recovers coeffs
72 EXPECT_EQ(values[k], values_copy[k]); // check that ifft does not modify input values
73 }
74}
75
76template <typename FF> class PolynomialTests : public ::testing::Test {};
77
78using FieldTypes = ::testing::Types<bb::fr, grumpkin::fr>;
79
81
82TYPED_TEST(PolynomialTests, linear_poly_product)
83{
84 using FF = TypeParam;
85 // Cover both BN254 and Grumpkin for the production SUBGROUP_SIZE range (87 Grumpkin, 256 BN254).
86 constexpr size_t n = 256;
88
90 FF expected = 1;
91 for (size_t i = 0; i < n; ++i) {
92 roots[i] = FF::random_element();
93 expected *= (z - roots[i]);
94 }
95
98 FF result = polynomial_arithmetic::evaluate(dest.data(), z, n + 1);
99
100 EXPECT_EQ(result, expected);
101}
102
103// compute_linear_polynomial_product handles the n=1 and n=2 boundaries of the incremental update.
104TYPED_TEST(PolynomialTests, LinearPolyProductSmallN)
105{
106 using FF = TypeParam;
107 // n=1: dest should represent (X - roots[0]) = [-r0, 1].
108 {
109 std::array<FF, 1> roots = { FF(7) };
110 std::array<FF, 2> dest{};
112 EXPECT_EQ(dest[0], -FF(7));
113 EXPECT_EQ(dest[1], FF(1));
114 }
115 // n=2: (X - 1)(X - 2) = X^2 - 3X + 2 → [2, -3, 1].
116 {
117 std::array<FF, 2> roots = { FF(1), FF(2) };
118 std::array<FF, 3> dest{};
120 EXPECT_EQ(dest[0], FF(2));
121 EXPECT_EQ(dest[1], -FF(3));
122 EXPECT_EQ(dest[2], FF(1));
123 }
124}
125
127{
128 using FF = TypeParam;
129 constexpr size_t n = 256;
130 auto domain = EvaluationDomain<FF>(n);
131
132 EXPECT_EQ(domain.size, 256UL);
133 EXPECT_EQ(domain.log2_size, 8UL);
134}
135
136// EvaluationDomain::operator=(EvaluationDomain&&) is a no-op under self-assignment and preserves
137// the precomputed round-roots tables.
138TYPED_TEST(PolynomialTests, EvaluationDomainMoveSelfAssign)
139{
140 using FF = TypeParam;
141 auto domain = EvaluationDomain<FF>(256);
142 domain.compute_lookup_table();
143 const size_t round_roots_before = domain.get_round_roots().size();
144 EXPECT_GT(round_roots_before, 0UL);
145
146 domain = std::move(domain);
147
148 EXPECT_EQ(domain.size, 256UL);
149 EXPECT_EQ(domain.get_round_roots().size(), round_roots_before);
150}
151
153{
154 using FF = TypeParam;
155 constexpr size_t n = 256;
156 auto domain = EvaluationDomain<FF>(n);
157
158 FF result;
159 FF expected;
160 expected = FF::one();
161 result = domain.root.pow(static_cast<uint64_t>(n));
162
163 EXPECT_EQ((result == expected), true);
164}
165
166TYPED_TEST(PolynomialTests, evaluation_domain_roots)
167{
168 using FF = TypeParam;
169 constexpr size_t n = 16;
170 EvaluationDomain<FF> domain(n);
171 domain.compute_lookup_table();
172 std::vector<FF*> root_table = domain.get_round_roots();
173 std::vector<FF*> inverse_root_table = domain.get_inverse_round_roots();
174 FF* roots = root_table[root_table.size() - 1];
175 FF* inverse_roots = inverse_root_table[inverse_root_table.size() - 1];
176 for (size_t i = 0; i < (n - 1) / 2; ++i) {
177 EXPECT_EQ(roots[i] * domain.root, roots[i + 1]);
178 EXPECT_EQ(inverse_roots[i] * domain.root_inverse, inverse_roots[i + 1]);
179 EXPECT_EQ(roots[i] * inverse_roots[i], FF::one());
180 }
181}
182
183TYPED_TEST(PolynomialTests, compute_efficient_interpolation)
184{
185 using FF = TypeParam;
186 constexpr size_t n = 250;
187 std::array<FF, n> src, poly, x;
188
189 for (size_t i = 0; i < n; ++i) {
190 poly[i] = FF::random_element();
191 }
192
193 for (size_t i = 0; i < n; ++i) {
194 x[i] = FF::random_element();
195 src[i] = polynomial_arithmetic::evaluate(poly.data(), x[i], n);
196 }
197 polynomial_arithmetic::compute_efficient_interpolation(src.data(), src.data(), x.data(), n);
198
199 for (size_t i = 0; i < n; ++i) {
200 EXPECT_EQ(src[i], poly[i]);
201 }
202}
203// Test efficient Lagrange interpolation when interpolation points contain 0
204TYPED_TEST(PolynomialTests, compute_efficient_interpolation_domain_with_zero)
205{
206 using FF = TypeParam;
207 constexpr size_t n = 15;
208 std::array<FF, n> src, poly, x;
209
210 for (size_t i = 0; i < n; ++i) {
211 poly[i] = FF(i + 1);
212 }
213
214 for (size_t i = 0; i < n; ++i) {
215 x[i] = FF(i);
216 src[i] = polynomial_arithmetic::evaluate(poly.data(), x[i], n);
217 }
218 polynomial_arithmetic::compute_efficient_interpolation(src.data(), src.data(), x.data(), n);
219
220 for (size_t i = 0; i < n; ++i) {
221 EXPECT_EQ(src[i], poly[i]);
222 }
223 // Test for the domain (-n/2, ..., 0, ... , n/2)
224
225 for (size_t i = 0; i < n; ++i) {
226 poly[i] = FF(i + 54);
227 }
228
229 for (size_t i = 0; i < n; ++i) {
230 x[i] = FF(i - n / 2);
231 src[i] = polynomial_arithmetic::evaluate(poly.data(), x[i], n);
232 }
233 polynomial_arithmetic::compute_efficient_interpolation(src.data(), src.data(), x.data(), n);
234
235 for (size_t i = 0; i < n; ++i) {
236 EXPECT_EQ(src[i], poly[i]);
237 }
238
239 // Test for the domain (-n+1, ..., 0)
240
241 for (size_t i = 0; i < n; ++i) {
242 poly[i] = FF(i * i + 57);
243 }
244
245 for (size_t i = 0; i < n; ++i) {
246 x[i] = FF(i - (n - 1));
247 src[i] = polynomial_arithmetic::evaluate(poly.data(), x[i], n);
248 }
249 polynomial_arithmetic::compute_efficient_interpolation(src.data(), src.data(), x.data(), n);
250
251 for (size_t i = 0; i < n; ++i) {
252 EXPECT_EQ(src[i], poly[i]);
253 }
254}
255
256TYPED_TEST(PolynomialTests, interpolation_constructor_single)
257{
258 using FF = TypeParam;
259
260 auto root = std::array{ FF(3) };
261 auto eval = std::array{ FF(4) };
262 Polynomial<FF> t(root, eval, 1);
263 ASSERT_EQ(t.size(), 1);
264 ASSERT_EQ(t[0], eval[0]);
265}
266
267TYPED_TEST(PolynomialTests, interpolation_constructor)
268{
269 using FF = TypeParam;
270
271 constexpr size_t N = 32;
272 std::array<FF, N> roots;
273 std::array<FF, N> evaluations;
274 for (size_t i = 0; i < N; ++i) {
275 roots[i] = FF::random_element();
276 evaluations[i] = FF::random_element();
277 }
278
279 auto roots_copy(roots);
280 auto evaluations_copy(evaluations);
281
282 Polynomial<FF> interpolated(roots, evaluations, N);
283
284 ASSERT_EQ(interpolated.size(), N);
285 ASSERT_EQ(roots, roots_copy);
286 ASSERT_EQ(evaluations, evaluations_copy);
287
288 for (size_t i = 0; i < N; ++i) {
289 FF eval = interpolated.evaluate(roots[i]);
290 ASSERT_EQ(eval, evaluations[i]);
291 }
292}
293
294TYPED_TEST(PolynomialTests, evaluate_mle_legacy)
295{
296 using FF = TypeParam;
297
298 auto test_case = [](size_t N) {
300 const size_t m = numeric::get_msb(N);
301 EXPECT_EQ(N, 1 << m);
302 Polynomial<FF> poly(N);
303 for (size_t i = 1; i < N - 1; ++i) {
304 poly.at(i) = FF::random_element(&engine);
305 }
306 poly.at(N - 1) = FF::zero();
307
308 EXPECT_TRUE(poly[0].is_zero());
309
310 // sample u = (u₀,…,uₘ₋₁)
311 std::vector<FF> u(m);
312 for (size_t l = 0; l < m; ++l) {
313 u[l] = FF::random_element(&engine);
314 }
315
316 std::vector<FF> lagrange_evals(N, FF(1));
317 for (size_t i = 0; i < N; ++i) {
318 auto& coef = lagrange_evals[i];
319 for (size_t l = 0; l < m; ++l) {
320 size_t mask = (1 << l);
321 if ((i & mask) == 0) {
322 coef *= (FF(1) - u[l]);
323 } else {
324 coef *= u[l];
325 }
326 }
327 }
328
329 // check eval by computing scalar product between
330 // lagrange evaluations and coefficients
331 FF real_eval(0);
332 for (size_t i = 0; i < N; ++i) {
333 real_eval += poly[i] * lagrange_evals[i];
334 }
335 FF computed_eval = poly.evaluate_mle(u);
336 EXPECT_EQ(real_eval, computed_eval);
337
338 // also check shifted eval
339 FF real_eval_shift(0);
340 for (size_t i = 1; i < N; ++i) {
341 real_eval_shift += poly[i] * lagrange_evals[i - 1];
342 }
343 FF computed_eval_shift = poly.evaluate_mle(u, true);
344 EXPECT_EQ(real_eval_shift, computed_eval_shift);
345 };
346 test_case(32);
347 test_case(4);
348 test_case(2);
349}
350
355TYPED_TEST(PolynomialTests, move_construct_and_assign)
356{
357 using FF = TypeParam;
358
359 // construct a poly with some arbitrary data
360 size_t num_coeffs = 64;
361 Polynomial<FF> polynomial_a(num_coeffs);
362 for (size_t i = 0; i < num_coeffs; i++) {
363 polynomial_a.at(i) = FF::random_element();
364 }
365
366 // construct a new poly from the original via the move constructor
367 Polynomial<FF> polynomial_b(std::move(polynomial_a));
368
369 // verifiy that source poly is appropriately destroyed
370 EXPECT_EQ(polynomial_a.data(), nullptr);
371
372 // construct another poly; this will also use the move constructor!
373 auto polynomial_c = std::move(polynomial_b);
374
375 // verifiy that source poly is appropriately destroyed
376 EXPECT_EQ(polynomial_b.data(), nullptr);
377
378 // define a poly with some arbitrary coefficients
379 Polynomial<FF> polynomial_d(num_coeffs);
380 for (size_t i = 0; i < num_coeffs; i++) {
381 polynomial_d.at(i) = FF::random_element();
382 }
383
384 // reset its data using move assignment
385 polynomial_d = std::move(polynomial_c);
386
387 // verifiy that source poly is appropriately destroyed
388 EXPECT_EQ(polynomial_c.data(), nullptr);
389}
390
391TYPED_TEST(PolynomialTests, default_construct_then_assign)
392{
393 using FF = TypeParam;
394
395 // construct an arbitrary but non-empty polynomial
396 size_t num_coeffs = 64;
397 Polynomial<FF> interesting_poly(num_coeffs);
398 for (size_t i = 0; i < num_coeffs; i++) {
399 interesting_poly.at(i) = FF::random_element();
400 }
401
402 // construct an empty poly via the default constructor
403 Polynomial<FF> poly;
404
405 EXPECT_EQ(poly.is_empty(), true);
406
407 // fill the empty poly using the assignment operator
408 poly = interesting_poly;
409
410 // coefficients and size should be equal in value
411 for (size_t i = 0; i < num_coeffs; ++i) {
412 EXPECT_EQ(poly[i], interesting_poly[i]);
413 }
414 EXPECT_EQ(poly.size(), interesting_poly.size());
415}
416
417// factor_roots produces the correct quotient when (X - r) divides p(X) cleanly.
418TEST(polynomials, FactorRootsExactDivisionRegression)
419{
420 using FF = fr;
421 // p(X) = X^2 - 1 = (X - 1)(X + 1): exact division by (X - 1) produces q(X) = X + 1.
422 std::array<FF, 3> exact_poly = { -FF(1), FF(0), FF(1) };
424 EXPECT_EQ(exact_poly[0], FF(1));
425 EXPECT_EQ(exact_poly[1], FF(1));
426 EXPECT_EQ(exact_poly[2], FF(0));
427}
428
429// factor_roots asserts when the exact-divisibility precondition is violated.
430TEST(polynomials, FactorRootsNonExactDivisionAsserts)
431{
432 GTEST_FLAG_SET(death_test_style, "threadsafe");
433 using FF = fr;
434 std::array<FF, 3> bad_poly = { FF(1), FF(0), FF(1) }; // p(X) = X^2 + 1, p(1) = 2 != 0
436}
437
438// Polynomial's interpolation constructor asserts when interpolation_points and evaluations differ in size.
439TEST(polynomials, InterpolationCtorMismatchedSpansAsserts)
440{
441 GTEST_FLAG_SET(death_test_style, "threadsafe");
442 using FF = fr;
443 std::vector<FF> points = { FF(1), FF(2), FF(3), FF(4) };
444 std::vector<FF> evals = { FF(10), FF(20) }; // shorter than points
446 bb::Polynomial<FF>(std::span<const FF>(points), std::span<const FF>(evals), /*virtual_size=*/4), ".*");
447}
448
449// parse_size_string throws when value * multiplier overflows size_t.
450TEST(polynomials, ParseSizeStringOverflowAsserts)
451{
452 // Sanity: well-formed inputs still parse correctly.
453 EXPECT_EQ(parse_size_string("1k"), 1024U);
454 EXPECT_EQ(parse_size_string("2g"), 2UL * 1024 * 1024 * 1024);
455
456 // 2^54 * 1024 == 2^64 wraps to 0 without a guard.
457 ASSERT_THROW_OR_ABORT(parse_size_string("18014398509481984k"), ".*");
458}
459
460#ifndef NDEBUG
461// compute_efficient_interpolation asserts (debug-only) when evaluation points are not all distinct.
462TEST(polynomials, ComputeEfficientInterpolationDuplicatePointsAsserts)
463{
464 GTEST_FLAG_SET(death_test_style, "threadsafe");
465 using FF = fr;
466 constexpr size_t n = 3;
467 std::array<FF, n> src = { FF(10), FF(20), FF(30) };
468 std::array<FF, n> dest{};
469 std::array<FF, n> points = { FF(1), FF(2), FF(2) }; // duplicate
471 polynomial_arithmetic::compute_efficient_interpolation<FF>(src.data(), dest.data(), points.data(), n), ".*");
472}
473#endif
474
475// fft_inner_parallel asserts when called in-place (coeffs == target).
476TEST(polynomials, FftInnerParallelInPlaceAsserts)
477{
478 GTEST_FLAG_SET(death_test_style, "threadsafe");
479 using FF = fr;
480 constexpr size_t n = 16;
481 auto domain = bb::EvaluationDomain<FF>(n);
482 domain.compute_lookup_table();
483
484 std::array<FF, n> coeffs{};
485 for (size_t i = 0; i < n; ++i) {
486 coeffs[i] = FF(i + 1);
487 }
489 polynomial_arithmetic::fft_inner_parallel(coeffs.data(), coeffs.data(), domain, FF(), domain.get_round_roots()),
490 ".*");
491}
#define ASSERT_THROW_OR_ABORT(statement, matcher)
Definition assert.hpp:191
size_t parse_size_string(const std::string &size_str)
const std::vector< FF * > & get_round_roots() const
const std::vector< FF * > & get_inverse_round_roots() const
Structured polynomial class that represents the coefficients 'a' of a_0 + a_1 x .....
bool is_empty() const
Fr evaluate(const Fr &z) const
Fr evaluate_mle(std::span< const Fr > evaluation_points, bool shift=false) const
evaluate multi-linear extension p(X_0,…,X_{n-1}) = \sum_i a_i*L_i(X_0,…,X_{n-1}) at u = (u_0,...
Fr & at(size_t index)
Our mutable accessor, unlike operator[]. We abuse precedent a bit to differentiate at() and operator[...
std::size_t size() const
numeric::RNG & engine
testing::Types< bb::fr > FieldTypes
constexpr T get_msb(const T in)
Definition get_msb.hpp:50
RNG & get_debug_randomness(bool reset, std::uint_fast64_t seed)
Definition engine.cpp:244
void ifft(Fr *coeffs, Fr *target, const EvaluationDomain< Fr > &domain)
void compute_linear_polynomial_product(const Fr *roots, Fr *dest, const size_t n)
Fr evaluate(const Fr *coeffs, const Fr &z, const size_t n)
void factor_roots(std::span< Fr > polynomial, const Fr &root)
Divides p(X) by (X-r) in-place.
void fft_inner_parallel(Fr *coeffs, Fr *target, const EvaluationDomain< Fr > &domain, const Fr &, const std::vector< Fr * > &root_table)
void compute_efficient_interpolation(const Fr *src, Fr *dest, const Fr *evaluation_points, const size_t n)
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
EvaluationDomain< bb::fr > evaluation_domain
TYPED_TEST_SUITE(CommitmentKeyTest, Curves)
field< Bn254FrParams > fr
Definition fr.hpp:155
TYPED_TEST(CommitmentKeyTest, CommitToZeroPoly)
TEST(BoomerangMegaCircuitBuilder, BasicCircuit)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
BB_INLINE constexpr field pow(const uint256_t &exponent) const noexcept
static field random_element(numeric::RNG *engine=nullptr) noexcept
static constexpr field zero()