Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
affine_add_packed.test.cpp
Go to the documentation of this file.
2
5
6#include <cstdint>
7#include <gtest/gtest.h>
8#include <span>
9#include <utility>
10#include <vector>
11
12namespace {
13
14using bb::fq;
16using Affine = bb::g1::affine_element;
18using VecFq = bb::VectorField<Params>;
19using PushSpanFq = bb::VectorFieldPushSpan<Params>;
20using AffinePushSpan = bb::VectorAffineElementPushSpan<Params>;
21using AffineCols = bb::AffineColumnSpan<fq>;
22
24
25// Packed batch_affine_add must reproduce the curve's own affine addition for every pair. Field
26// arithmetic is exact, so a correct add yields identical coordinates. Checked across boundary sizes.
27TEST(BatchAffineAddPacked, MatchesCurveAffineAdd)
28{
29 constexpr size_t W = PushSpanFq::W;
30 for (size_t n : { size_t{ 1 }, size_t{ 4 }, size_t{ 5 }, size_t{ 6 }, size_t{ 9 }, size_t{ 25 }, size_t{ 31 } }) {
34 for (size_t i = 0; i < n; ++i) {
35 Affine a = Element::random_element(&engine);
36 Affine b = Element::random_element(&engine);
37 while (a.x == b.x) { // the unsafe add needs distinct x (dx != 0); independent points satisfy this
38 b = Affine(Element::random_element(&engine));
39 }
40 L[i] = a;
41 R[i] = b;
42 ref[i] = a + b; // ground-truth affine add
43 }
44
45 std::vector<VecFq> lx((n / W) + 1), ly((n / W) + 1), rx((n / W) + 1), ry((n / W) + 1);
46 std::vector<VecFq> ox((n / W) + 1), oy((n / W) + 1);
47 std::vector<VecFq> dx((n / W) + 1), dy((n / W) + 1), xsum((n / W) + 1), inv((n / W) + 1);
48 AffinePushSpan lhs{ std::span<VecFq>(lx), std::span<VecFq>(ly) };
49 AffinePushSpan rhs{ std::span<VecFq>(rx), std::span<VecFq>(ry) };
50 AffinePushSpan out{ std::span<VecFq>(ox), std::span<VecFq>(oy) };
51 for (size_t i = 0; i < n; ++i) {
52 lhs.push_point(L[i].x, L[i].y);
53 rhs.push_point(R[i].x, R[i].y);
54 }
56 PushSpanFq{ std::span<VecFq>(dy) },
57 PushSpanFq{ std::span<VecFq>(xsum) },
58 PushSpanFq{ std::span<VecFq>(inv) } };
59
61
62 ASSERT_EQ(out.size(), n);
63 for (size_t i = 0; i < n; ++i) {
64 const bool full = i < out.num_full_vectors() * W;
65 const fq gx = full ? out.x[i / W].to_array()[i % W] : out.x.tail_data()[i % W];
66 const fq gy = full ? out.y[i / W].to_array()[i % W] : out.y.tail_data()[i % W];
67 EXPECT_EQ(gx, ref[i].x) << "n=" << n << " i=" << i;
68 EXPECT_EQ(gy, ref[i].y) << "n=" << n << " i=" << i;
69 }
70 }
71}
72
73// The fast-Pippenger drain runs batch_affine_add in place: out shares lhs's backing, so each result
74// overwrites its own input. finish computes x3/y3 before writing either, so this must still match the
75// curve's affine add. Constructs out over lhs's coordinate spans and checks bit-exactness.
76TEST(BatchAffineAddPacked, OutAliasesLhs)
77{
78 constexpr size_t W = PushSpanFq::W;
79 for (size_t n : { size_t{ 1 }, size_t{ 5 }, size_t{ 6 }, size_t{ 25 }, size_t{ 31 } }) {
83 for (size_t i = 0; i < n; ++i) {
84 Affine a = Element::random_element(&engine);
85 Affine b = Element::random_element(&engine);
86 while (a.x == b.x) {
87 b = Affine(Element::random_element(&engine));
88 }
89 L[i] = a;
90 R[i] = b;
91 ref[i] = a + b;
92 }
93
94 std::vector<VecFq> lx((n / W) + 1), ly((n / W) + 1), rx((n / W) + 1), ry((n / W) + 1);
95 std::vector<VecFq> dx((n / W) + 1), dy((n / W) + 1), xsum((n / W) + 1), inv((n / W) + 1);
96 AffinePushSpan lhs{ std::span<VecFq>(lx), std::span<VecFq>(ly) };
97 AffinePushSpan rhs{ std::span<VecFq>(rx), std::span<VecFq>(ry) };
98 AffinePushSpan out{ std::span<VecFq>(lx), std::span<VecFq>(ly) }; // shares lhs's backing
99 for (size_t i = 0; i < n; ++i) {
100 lhs.push_point(L[i].x, L[i].y);
101 rhs.push_point(R[i].x, R[i].y);
102 }
104 PushSpanFq{ std::span<VecFq>(dy) },
105 PushSpanFq{ std::span<VecFq>(xsum) },
106 PushSpanFq{ std::span<VecFq>(inv) } };
107
109
110 ASSERT_EQ(out.size(), n);
111 for (size_t i = 0; i < n; ++i) {
112 const bool full = i < out.num_full_vectors() * W;
113 const fq gx = full ? out.x[i / W].to_array()[i % W] : out.x.tail_data()[i % W];
114 const fq gy = full ? out.y[i / W].to_array()[i % W] : out.y.tail_data()[i % W];
115 EXPECT_EQ(gx, ref[i].x) << "n=" << n << " i=" << i;
116 EXPECT_EQ(gy, ref[i].y) << "n=" << n << " i=" << i;
117 }
118 }
119}
120
121// Packed batch_affine_double must reproduce the curve's own affine doubling for every point. Checked
122// across boundary sizes.
123TEST(BatchAffineAddPacked, MatchesCurveDouble)
124{
125 constexpr size_t W = PushSpanFq::W;
126 for (size_t n : { size_t{ 1 }, size_t{ 4 }, size_t{ 5 }, size_t{ 6 }, size_t{ 9 }, size_t{ 25 }, size_t{ 31 } }) {
128 std::vector<Affine> ref(n);
129 for (size_t i = 0; i < n; ++i) {
130 Affine a = Element::random_element(&engine);
131 P[i] = a;
132 Element d(a);
133 d.self_dbl();
134 ref[i] = Affine(d); // ground-truth doubling
135 }
136
137 std::vector<VecFq> px((n / W) + 1), py((n / W) + 1);
138 std::vector<VecFq> ox((n / W) + 1), oy((n / W) + 1);
139 std::vector<VecFq> den((n / W) + 1), num((n / W) + 1), inv((n / W) + 1);
140 AffinePushSpan in{ std::span<VecFq>(px), std::span<VecFq>(py) };
141 AffinePushSpan out{ std::span<VecFq>(ox), std::span<VecFq>(oy) };
142 for (size_t i = 0; i < n; ++i) {
143 in.push_point(P[i].x, P[i].y);
144 }
146 PushSpanFq{ std::span<VecFq>(num) },
147 PushSpanFq{ std::span<VecFq>(inv) } };
148
150
151 ASSERT_EQ(out.size(), n);
152 for (size_t i = 0; i < n; ++i) {
153 const bool full = i < out.num_full_vectors() * W;
154 const fq gx = full ? out.x[i / W].to_array()[i % W] : out.x.tail_data()[i % W];
155 const fq gy = full ? out.y[i / W].to_array()[i % W] : out.y.tail_data()[i % W];
156 EXPECT_EQ(gx, ref[i].x) << "n=" << n << " i=" << i;
157 EXPECT_EQ(gy, ref[i].y) << "n=" << n << " i=" << i;
158 }
159 }
160}
161
162// The indexed packed add gathers operands out of a bucket array by pair-index and scatters the sums
163// back, so it must reproduce the scalar batch_affine_add_indexed_impl (the in-use Stage 6b kernel)
164// bit-for-bit on the same buckets and pairs. dst indices are distinct and disjoint from src (the
165// reduction's invariant); sizes straddle the wrapper's internal chunk boundary (capacity ~65) to
166// exercise single- and multi-chunk dispatch.
167TEST(BatchAffineAddPacked, IndexedAddMatchesScalar)
168{
169 constexpr size_t W = PushSpanFq::W;
170 constexpr size_t CAP_VFS = (64 / W) + 1; // chunk capacity = CAP_VFS * W ~ 65 elements
171 for (size_t np : { size_t{ 1 },
172 size_t{ 4 },
173 size_t{ 5 },
174 size_t{ 6 },
175 size_t{ 31 },
176 size_t{ 65 },
177 size_t{ 130 },
178 size_t{ 300 } }) {
179 std::vector<Affine> base(2 * np);
181 for (size_t k = 0; k < np; ++k) {
182 Affine a = Element::random_element(&engine);
183 Affine b = Element::random_element(&engine);
184 while (a.x == b.x) { // distinct x: the unsafe add's precondition, and what try_filter_pair guarantees
185 b = Affine(Element::random_element(&engine));
186 }
187 base[k] = a; // dst slot
188 base[np + k] = b; // src slot (disjoint from the dst range)
189 pairs[k] = { static_cast<uint32_t>(k), static_cast<uint32_t>(np + k) };
190 }
191
192 std::vector<Affine> scalar_buckets = base;
193 std::vector<fq> scalar_scratch(np);
194 bb::group_elements::batch_affine_add_indexed_impl<Affine, fq>(
195 scalar_buckets.data(), pairs.data(), np, scalar_scratch.data());
196
197 // packed path runs over the column (SoA) view of the same points
198 std::vector<fq> col_x(base.size());
199 std::vector<fq> col_y(base.size());
200 for (size_t i = 0; i < base.size(); ++i) {
201 col_x[i] = base[i].x;
202 col_y[i] = base[i].y;
203 }
204 AffineCols cols{ std::span<fq>(col_x), std::span<fq>(col_y) };
205 std::vector<VecFq> lx(CAP_VFS), ly(CAP_VFS), rx(CAP_VFS), ry(CAP_VFS), ox(CAP_VFS), oy(CAP_VFS);
206 std::vector<VecFq> dx(CAP_VFS), dy(CAP_VFS), xsum(CAP_VFS), inv(CAP_VFS);
207 AffinePushSpan lhs{ std::span<VecFq>(lx), std::span<VecFq>(ly) };
208 AffinePushSpan rhs{ std::span<VecFq>(rx), std::span<VecFq>(ry) };
209 AffinePushSpan out{ std::span<VecFq>(ox), std::span<VecFq>(oy) };
211 PushSpanFq{ std::span<VecFq>(dy) },
212 PushSpanFq{ std::span<VecFq>(xsum) },
213 PushSpanFq{ std::span<VecFq>(inv) } };
214
216
217 for (size_t k = 0; k < np; ++k) {
218 EXPECT_EQ(col_x[k], scalar_buckets[k].x) << "np=" << np << " k=" << k;
219 EXPECT_EQ(col_y[k], scalar_buckets[k].y) << "np=" << np << " k=" << k;
220 }
221 }
222}
223
224// The indexed packed double gathers each point by index and scatters its double back, so it must
225// reproduce the scalar batch_affine_double_indexed_impl bit-for-bit. Sizes straddle the chunk boundary.
226TEST(BatchAffineAddPacked, IndexedDoubleMatchesScalar)
227{
228 constexpr size_t W = PushSpanFq::W;
229 constexpr size_t CAP_VFS = (64 / W) + 1;
230 for (size_t np :
231 { size_t{ 1 }, size_t{ 5 }, size_t{ 6 }, size_t{ 31 }, size_t{ 65 }, size_t{ 130 }, size_t{ 300 } }) {
232 std::vector<Affine> base(np);
233 std::vector<uint32_t> indices(np);
234 for (size_t k = 0; k < np; ++k) {
235 base[k] = Affine(Element::random_element(&engine));
236 indices[k] = static_cast<uint32_t>(k);
237 }
238
239 std::vector<Affine> scalar_buckets = base;
240 std::vector<fq> scalar_scratch(np);
241 bb::group_elements::batch_affine_double_indexed_impl<Affine, fq>(
242 scalar_buckets.data(), indices.data(), np, scalar_scratch.data());
243
244 std::vector<fq> col_x(np);
245 std::vector<fq> col_y(np);
246 for (size_t i = 0; i < np; ++i) {
247 col_x[i] = base[i].x;
248 col_y[i] = base[i].y;
249 }
250 AffineCols cols{ std::span<fq>(col_x), std::span<fq>(col_y) };
251 std::vector<VecFq> px(CAP_VFS), py(CAP_VFS), ox(CAP_VFS), oy(CAP_VFS);
252 std::vector<VecFq> den(CAP_VFS), num(CAP_VFS), inv(CAP_VFS);
253 AffinePushSpan in{ std::span<VecFq>(px), std::span<VecFq>(py) };
254 AffinePushSpan out{ std::span<VecFq>(ox), std::span<VecFq>(oy) };
256 PushSpanFq{ std::span<VecFq>(num) },
257 PushSpanFq{ std::span<VecFq>(inv) } };
258
260
261 for (size_t k = 0; k < np; ++k) {
262 EXPECT_EQ(col_x[k], scalar_buckets[k].x) << "np=" << np << " k=" << k;
263 EXPECT_EQ(col_y[k], scalar_buckets[k].y) << "np=" << np << " k=" << k;
264 }
265 }
266}
267
268// The scalar SoA twins are the native path of the Stage 6b reduction, so they must reproduce the AoS
269// scalar *_indexed_impl (the kernel they replace) bit-for-bit on the same points.
270TEST(BatchAffineAddPacked, IndexedAddScalarSoaMatchesAos)
271{
272 for (size_t np : { size_t{ 1 }, size_t{ 5 }, size_t{ 31 }, size_t{ 130 }, size_t{ 300 } }) {
273 std::vector<Affine> base(2 * np);
275 for (size_t k = 0; k < np; ++k) {
276 Affine a = Element::random_element(&engine);
277 Affine b = Element::random_element(&engine);
278 while (a.x == b.x) {
279 b = Affine(Element::random_element(&engine));
280 }
281 base[k] = a;
282 base[np + k] = b;
283 pairs[k] = { static_cast<uint32_t>(k), static_cast<uint32_t>(np + k) };
284 }
285
286 std::vector<Affine> aos = base;
287 std::vector<fq> aos_scratch(np);
288 bb::group_elements::batch_affine_add_indexed_impl<Affine, fq>(aos.data(), pairs.data(), np, aos_scratch.data());
289
290 std::vector<fq> col_x(base.size());
291 std::vector<fq> col_y(base.size());
292 for (size_t i = 0; i < base.size(); ++i) {
293 col_x[i] = base[i].x;
294 col_y[i] = base[i].y;
295 }
296 AffineCols cols{ std::span<fq>(col_x), std::span<fq>(col_y) };
297 std::vector<fq> soa_scratch(np);
298 bb::group_elements::batch_affine_add_indexed_scalar(cols, pairs.data(), np, soa_scratch.data());
299
300 for (size_t k = 0; k < np; ++k) {
301 EXPECT_EQ(col_x[k], aos[k].x) << "np=" << np << " k=" << k;
302 EXPECT_EQ(col_y[k], aos[k].y) << "np=" << np << " k=" << k;
303 }
304 }
305}
306
307TEST(BatchAffineAddPacked, IndexedDoubleScalarSoaMatchesAos)
308{
309 for (size_t np : { size_t{ 1 }, size_t{ 5 }, size_t{ 31 }, size_t{ 130 }, size_t{ 300 } }) {
310 std::vector<Affine> base(np);
311 std::vector<uint32_t> indices(np);
312 for (size_t k = 0; k < np; ++k) {
313 base[k] = Affine(Element::random_element(&engine));
314 indices[k] = static_cast<uint32_t>(k);
315 }
316
317 std::vector<Affine> aos = base;
318 std::vector<fq> aos_scratch(np);
319 bb::group_elements::batch_affine_double_indexed_impl<Affine, fq>(
320 aos.data(), indices.data(), np, aos_scratch.data());
321
322 std::vector<fq> col_x(np);
323 std::vector<fq> col_y(np);
324 for (size_t i = 0; i < np; ++i) {
325 col_x[i] = base[i].x;
326 col_y[i] = base[i].y;
327 }
328 AffineCols cols{ std::span<fq>(col_x), std::span<fq>(col_y) };
329 std::vector<fq> soa_scratch(np);
330 bb::group_elements::batch_affine_double_indexed_scalar(cols, indices.data(), np, soa_scratch.data());
331
332 for (size_t k = 0; k < np; ++k) {
333 EXPECT_EQ(col_x[k], aos[k].x) << "np=" << np << " k=" << k;
334 EXPECT_EQ(col_y[k], aos[k].y) << "np=" << np << " k=" << k;
335 }
336 }
337}
338
339} // namespace
TEST(acir_formal_proofs, uint_terms_add)
Tests 128-bit unsigned addition Verifies that the ACIR implementation of addition is correct Executio...
Parameters defining the base field of the BN254 curve.
Definition fq.hpp:28
group_elements::affine_element< Fq, Fr, Params > affine_element
Definition group.hpp:44
group_elements::element< Fq, Fr, Params > element
Definition group.hpp:43
FF a
FF b
numeric::RNG & engine
void batch_affine_add_indexed_scalar(AffineColumnSpan< Field > &buckets, const std::pair< uint32_t, uint32_t > *pairs, size_t num_pairs, Field *scratch) noexcept
const uint32_t * indices
void batch_affine_add(const VectorAffineElementPushSpan< Params > &lhs, const VectorAffineElementPushSpan< Params > &rhs, VectorAffineElementPushSpan< Params > &out, BatchAffineAddScratch< Params > &s) noexcept
void batch_affine_double(const VectorAffineElementPushSpan< Params > &in, VectorAffineElementPushSpan< Params > &out, BatchAffineDoubleScratch< Params > &s) noexcept
void batch_affine_double_indexed_scalar(AffineColumnSpan< Field > &buckets, const uint32_t *indices, size_t num_points, Field *scratch) noexcept
void batch_affine_double_indexed_packed(AffineColumnSpan< typename VectorField< Params >::Field > &buckets, const uint32_t *indices, size_t num_points, VectorAffineElementPushSpan< Params > &in, VectorAffineElementPushSpan< Params > &out, BatchAffineDoubleScratch< Params > &scratch) noexcept
const std::pair< uint32_t, uint32_t > * pairs
void batch_affine_add_indexed_packed(AffineColumnSpan< typename VectorField< Params >::Field > &buckets, const std::pair< uint32_t, uint32_t > *pairs, size_t num_pairs, VectorAffineElementPushSpan< Params > &lhs, VectorAffineElementPushSpan< Params > &rhs, VectorAffineElementPushSpan< Params > &out, BatchAffineAddScratch< Params > &scratch) noexcept
RNG & get_debug_randomness(bool reset, std::uint_fast64_t seed)
Definition engine.cpp:245
field< Bn254FqParams > fq
Definition fq.hpp:153
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
bb::VectorAffineElementPushSpan< BaseParams > lhs
bb::VectorAffineElementPushSpan< BaseParams > out
bb::VectorAffineElementPushSpan< BaseParams > rhs
Curve::Element Element