Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
ecc_ops_table.test.cpp
Go to the documentation of this file.
5#include <gtest/gtest.h>
6
7#include <ranges>
8
9using namespace bb;
10
11class EccOpsTableTest : public ::testing::Test {
13 using Scalar = fr;
14
15 public:
16 template <typename Op> struct MockSubtableGenerator {
17 virtual ~MockSubtableGenerator() = default;
18 virtual Op generate_random_op() const = 0;
19 std::vector<std::vector<Op>> generate_subtables(size_t num_subtables, std::vector<size_t> ops_per_table)
20 {
21 BB_ASSERT_EQ(num_subtables, ops_per_table.size());
23 subtables.reserve(num_subtables);
24 for (size_t i = 0; i < num_subtables; ++i) {
25 std::vector<Op> subtable;
26 subtable.reserve(ops_per_table[i]);
27 for (size_t j = 0; j < ops_per_table[i]; ++j) {
28 subtable.push_back(generate_random_op());
29 }
30 subtables.push_back(std::move(subtable));
31 }
32
33 return subtables;
34 }
35 };
36
38 ~UltraOpTableGenerator() override = default;
39 UltraOp generate_random_op() const override
40 {
41 return UltraOp{ .op_code = EccOpCode{},
42 .x_lo = Scalar::random_element(),
43 .x_hi = Scalar::random_element(),
44 .y_lo = Scalar::random_element(),
45 .y_hi = Scalar::random_element(),
48 .return_is_infinity = false };
49 }
50 };
51
52 struct EccvmOpTableGenerator : public MockSubtableGenerator<ECCVMOperation> {
53 ~EccvmOpTableGenerator() override = default;
55 {
56 return ECCVMOperation{ .op_code = EccOpCode{ .mul = true },
57 .base_point = Curve::Group::affine_element::random_element(),
60 .mul_scalar_full = Scalar::random_element() };
61 }
62 };
63
64 // Mock ultra ops table that constructs a concatenated table from successively prepended subtables
67 void append(const UltraOp& op)
68 {
69 columns[0].push_back(op.op_code.value());
70 columns[1].push_back(op.x_lo);
71 columns[2].push_back(op.x_hi);
72 columns[3].push_back(op.y_lo);
73
74 columns[0].push_back(0);
75 columns[1].push_back(op.y_hi);
76 columns[2].push_back(op.z_1);
77 columns[3].push_back(op.z_2);
78 }
79
80 // Construct the ultra ops table from the given subtables, ordered as they should appear in the op queue.
81 MockUltraOpsTable(const auto& subtable_ops)
82 {
83 for (auto& ops : subtable_ops) {
84 for (const auto& op : ops) {
85 append(op);
86 }
87 }
88 }
89
90 size_t size() const { return columns[0].size(); }
91 };
92
93 // Mock eccvm ops table that constructs a concatenated table from successively prepended subtables
95 std::vector<ECCVMOperation> eccvm_ops;
96
97 MockEccvmOpsTable(const auto& subtable_ops)
98 {
99 for (auto& ops : subtable_ops) {
100 for (const auto& op : ops) {
101 eccvm_ops.push_back(op);
102 }
103 }
104 }
105 };
106};
107
108// Ensure UltraOpsTable correctly constructs a concatenated table from successively prepended subtables
109TEST(EccOpsTableTest, UltraOpsTablePrependOnly)
110{
111 using Fr = fr;
112 using TableGenerator = EccOpsTableTest::UltraOpTableGenerator;
113
114 // Construct sets of ultra ops, each representing those added by a single circuit
115 const size_t NUM_SUBTABLES = 3;
116 std::vector<size_t> subtable_op_counts = { 4, 2, 7 };
117
118 TableGenerator table_generator;
119 auto subtables = table_generator.generate_subtables(NUM_SUBTABLES, subtable_op_counts);
120
121 // Construct the concatenated table internal to the op queue
122 UltraEccOpsTable ultra_ops_table;
123 for (const auto& subtable_ops : subtables) {
124 ultra_ops_table.create_new_subtable();
125 for (const auto& op : subtable_ops) {
126 ultra_ops_table.push(op);
127 }
128 ultra_ops_table.merge();
129 }
130
131 std::reverse(subtables.begin(), subtables.end());
132 // Construct the mock ultra ops table which contains the subtables ordered in reverse (as if prepended)
133 EccOpsTableTest::MockUltraOpsTable expected_ultra_ops_table(subtables);
134
135 // Check that the ultra ops table internal to the op queue has the correct size
136 auto expected_num_ops = std::accumulate(subtable_op_counts.begin(), subtable_op_counts.end(), size_t(0));
137 EXPECT_EQ(ultra_ops_table.size(), expected_num_ops);
138
139 // Construct polynomials corresponding to the columns of the ultra ops table
140 std::array<Polynomial<Fr>, 4> ultra_ops_table_polynomials = ultra_ops_table.construct_table_columns();
141
142 // Check that the ultra ops table constructed by the op queue matches the expected table
143 for (auto [expected_column, poly] : zip_view(expected_ultra_ops_table.columns, ultra_ops_table_polynomials)) {
144 for (auto [expected_value, value] : zip_view(expected_column, poly.coeffs())) {
145 EXPECT_EQ(expected_value, value);
146 }
147 }
148}
149
150TEST(EccOpsTableTest, UltraOpsPrependThenAppend)
151{
152 using Fr = fr;
153 using TableGenerator = EccOpsTableTest::UltraOpTableGenerator;
154
155 // Construct sets of ultra ops, each representing those added by a single circuit
156 const size_t NUM_SUBTABLES = 3;
157 std::vector<size_t> subtable_op_counts = { 4, 2, 7 };
158
159 TableGenerator table_generator;
160 auto subtables = table_generator.generate_subtables(NUM_SUBTABLES, subtable_op_counts);
161
162 // Construct the concatenated table internal to the op queue
163 UltraEccOpsTable ultra_ops_table;
164 std::array<MergeSettings, NUM_SUBTABLES> merge_settings = { MergeSettings::PREPEND,
165 MergeSettings::PREPEND,
166 MergeSettings::APPEND };
167 for (const auto& [subtable_ops, setting] : zip_view(subtables, merge_settings)) {
168 ultra_ops_table.create_new_subtable();
169 for (const auto& op : subtable_ops) {
170 ultra_ops_table.push(op);
171 }
172 ultra_ops_table.merge(setting);
173 }
174
175 std::vector<std::vector<UltraOp>> ordered_subtables;
176 for (auto [subtable, setting] : zip_view(subtables, merge_settings)) {
177 auto it = setting == MergeSettings::PREPEND ? ordered_subtables.begin() : ordered_subtables.end();
178 ordered_subtables.insert(it, subtable);
179 }
180
181 // Construct the mock ultra ops table which contains the subtables ordered in reverse (as if prepended)
182 EccOpsTableTest::MockUltraOpsTable expected_ultra_ops_table(ordered_subtables);
183
184 // Check that the ultra ops table internal to the op queue has the correct size
185 auto expected_num_ops = std::accumulate(subtable_op_counts.begin(), subtable_op_counts.end(), size_t(0));
186 EXPECT_EQ(ultra_ops_table.size(), expected_num_ops);
187
188 // Construct polynomials corresponding to the columns of the ultra ops table
189 std::array<Polynomial<Fr>, 4> ultra_ops_table_polynomials = ultra_ops_table.construct_table_columns();
190
191 // Check that the ultra ops table constructed by the op queue matches the expected table
192 for (auto [expected_column, poly] : zip_view(expected_ultra_ops_table.columns, ultra_ops_table_polynomials)) {
193 for (auto [expected_value, value] : zip_view(expected_column, poly.coeffs())) {
194 EXPECT_EQ(expected_value, value);
195 }
196 }
197}
198
199TEST(EccOpsTableTest, UltraOpsFixedLocationAppendNoGap)
200{
201 using Fr = fr;
202 using TableGenerator = EccOpsTableTest::UltraOpTableGenerator;
203
204 // Construct sets of ultra ops
205 const size_t NUM_SUBTABLES = 3;
206 std::vector<size_t> subtable_op_counts = { 4, 2, 7 };
207
208 TableGenerator table_generator;
209 auto subtables = table_generator.generate_subtables(NUM_SUBTABLES, subtable_op_counts);
210
211 // Construct the concatenated table with fixed-location append (no explicit offset)
212 UltraEccOpsTable ultra_ops_table;
213 std::array<MergeSettings, NUM_SUBTABLES> merge_settings = { MergeSettings::PREPEND,
214 MergeSettings::PREPEND,
215 MergeSettings::APPEND };
216
217 for (size_t i = 0; i < NUM_SUBTABLES; ++i) {
218 ultra_ops_table.create_new_subtable();
219 for (const auto& op : subtables[i]) {
220 ultra_ops_table.push(op);
221 }
222
223 // For APPEND (last subtable), don't provide an offset (default to right after prepended tables)
224 ultra_ops_table.merge(merge_settings[i]);
225 }
226
227 // Expected order: subtable[1], subtable[0], subtable[2] (no gap)
228 std::vector<std::vector<UltraOp>> ordered_subtables = { subtables[1], subtables[0], subtables[2] };
229
230 // Construct the mock ultra ops table
231 EccOpsTableTest::MockUltraOpsTable expected_ultra_ops_table(ordered_subtables);
232
233 // Check that the ultra ops table has the correct size
234 auto expected_num_ops = std::accumulate(subtable_op_counts.begin(), subtable_op_counts.end(), size_t(0));
235 EXPECT_EQ(ultra_ops_table.size(), expected_num_ops);
236
237 // Construct polynomials corresponding to the columns of the ultra ops table
238 std::array<Polynomial<Fr>, 4> ultra_ops_table_polynomials = ultra_ops_table.construct_table_columns();
239
240 // Check that the ultra ops table matches the expected table
241 for (auto [expected_column, poly] : zip_view(expected_ultra_ops_table.columns, ultra_ops_table_polynomials)) {
242 for (auto [expected_value, value] : zip_view(expected_column, poly.coeffs())) {
243 EXPECT_EQ(expected_value, value);
244 }
245 }
246}
247
248TEST(EccOpsTableTest, UltraOpsFixedLocationAppendWithGap)
249{
250 using Fr = fr;
251 using TableGenerator = EccOpsTableTest::UltraOpTableGenerator;
252
253 const size_t ULTRA_ROWS_PER_OP = UltraEccOpsTable::NUM_ROWS_PER_OP;
254
255 // Construct sets of ultra ops
256 const size_t NUM_SUBTABLES = 3;
257 std::vector<size_t> subtable_op_counts = { 4, 2, 7 };
258
259 TableGenerator table_generator;
260 auto subtables = table_generator.generate_subtables(NUM_SUBTABLES, subtable_op_counts);
261
262 // Construct the concatenated table with fixed-location append at specific offset
263 UltraEccOpsTable ultra_ops_table;
264 std::array<MergeSettings, NUM_SUBTABLES> merge_settings = { MergeSettings::PREPEND,
265 MergeSettings::PREPEND,
266 MergeSettings::APPEND };
267
268 // Define a fixed offset at which to append the table (must be greater than the total size of the prepended tables)
269 const size_t fixed_offset = 20;
270 const size_t prepended_size = (subtable_op_counts[0] + subtable_op_counts[1]) * ULTRA_ROWS_PER_OP;
271 ASSERT(fixed_offset > prepended_size);
272
273 // Construct the ultra ops table
274 for (size_t i = 0; i < NUM_SUBTABLES; ++i) {
275 ultra_ops_table.create_new_subtable();
276 for (const auto& op : subtables[i]) {
277 ultra_ops_table.push(op);
278 }
279
280 // For APPEND (last subtable), provide a fixed offset
281 if (merge_settings[i] == MergeSettings::APPEND) {
282 ultra_ops_table.merge(merge_settings[i], fixed_offset);
283 } else {
284 ultra_ops_table.merge(merge_settings[i]);
285 }
286 }
287
288 // Check that the ultra ops table has the correct total size (gap is not present in raw ops table)
289 auto expected_num_ops = std::accumulate(subtable_op_counts.begin(), subtable_op_counts.end(), size_t(0));
290 EXPECT_EQ(ultra_ops_table.size(), expected_num_ops);
291
292 // Check that the polynomials have the correct size (including gap)
293 size_t expected_poly_size = fixed_offset + (subtable_op_counts[2] * ULTRA_ROWS_PER_OP);
294 EXPECT_EQ(ultra_ops_table.ultra_table_size(), expected_poly_size);
295
296 // Construct polynomials corresponding to the columns of the ultra ops table
297 std::array<Polynomial<Fr>, 4> ultra_ops_table_polynomials = ultra_ops_table.construct_table_columns();
298
299 // Verify each polynomial has the expected size
300 for (const auto& poly : ultra_ops_table_polynomials) {
301 EXPECT_EQ(poly.size(), expected_poly_size);
302 }
303
304 // Construct expected table with zeros in the gap
305 // Order: subtable[1], subtable[0], zeros, subtable[2]
306 std::vector<std::vector<UltraOp>> ordered_subtables = { subtables[1], subtables[0] };
307 EccOpsTableTest::MockUltraOpsTable expected_prepended_table(ordered_subtables);
308
309 // Check prepended subtables are at the beginning
310 for (auto [ultra_op_poly, expected_poly] :
311 zip_view(ultra_ops_table_polynomials, expected_prepended_table.columns)) {
312 for (size_t row = 0; row < prepended_size; ++row) {
313 EXPECT_EQ(ultra_op_poly.at(row), expected_poly[row]);
314 }
315 }
316
317 // Check gap from offset to appended subtable is filled with zeros
318 for (auto ultra_op_poly : ultra_ops_table_polynomials) {
319 for (size_t row = prepended_size; row < fixed_offset; ++row) {
320 EXPECT_EQ(ultra_op_poly.at(row), Fr::zero());
321 }
322 }
323
324 // Check appended subtable is at the fixed offset
325 std::vector<std::vector<UltraOp>> appended_subtables = { subtables[2] };
326 EccOpsTableTest::MockUltraOpsTable expected_appended_table(appended_subtables);
327 for (auto [ultra_op_poly, expected_poly] : zip_view(ultra_ops_table_polynomials, expected_appended_table.columns)) {
328 for (size_t row = 0; row < subtable_op_counts[2] * ULTRA_ROWS_PER_OP; ++row) {
329 EXPECT_EQ(ultra_op_poly.at(fixed_offset + row), expected_poly[row]);
330 }
331 }
332}
333
334// Ensure EccvmOpsTable correctly constructs a concatenated table from successively prepended subtables
336{
337
338 // Construct sets of eccvm ops, each representing those added by a single circuit
339 using TableGenerator = EccOpsTableTest::EccvmOpTableGenerator;
340
341 // Construct sets of ultra ops, each representing those added by a single circuit
342 const size_t NUM_SUBTABLES = 3;
343 std::vector<size_t> subtable_op_counts = { 4, 2, 7 };
344
345 TableGenerator table_generator;
346 auto subtables = table_generator.generate_subtables(NUM_SUBTABLES, subtable_op_counts);
347
348 // Construct the concatenated eccvm ops table
349 EccvmOpsTable eccvm_ops_table;
350 for (const auto& subtable_ops : subtables) {
351 eccvm_ops_table.create_new_subtable();
352 for (const auto& op : subtable_ops) {
353 eccvm_ops_table.push(op);
354 }
355 eccvm_ops_table.merge();
356 }
357
358 std::reverse(subtables.begin(), subtables.end());
359 // Construct the mock eccvm ops table which contains the subtables ordered in reverse (as if prepended)
360 EccOpsTableTest::MockEccvmOpsTable expected_eccvm_ops_table(subtables);
361
362 // Check that the table has the correct size
363 auto expected_num_ops = std::accumulate(subtable_op_counts.begin(), subtable_op_counts.end(), size_t(0));
364 EXPECT_EQ(eccvm_ops_table.size(), expected_num_ops);
365
366 // Check that accessing the table values via operator[] matches the manually constructed mock table
367 for (size_t i = 0; i < expected_num_ops; ++i) {
368 EXPECT_EQ(expected_eccvm_ops_table.eccvm_ops[i], eccvm_ops_table[i]);
369 }
370
371 // Check that the copy-based reconstruction of the eccvm ops table matches the expected table
372 EXPECT_EQ(expected_eccvm_ops_table.eccvm_ops, eccvm_ops_table.get_reconstructed());
373}
374
375// Ensure EccvmOpsTable correctly constructs a concatenated table from successively prepended and then appended
376// subtables
377TEST(EccOpsTableTest, EccvmOpsTablePrependThenAppend)
378{
379
380 // Construct sets of eccvm ops, each representing those added by a single circuit
381 using TableGenerator = EccOpsTableTest::EccvmOpTableGenerator;
382
383 // Construct sets of ops, each representing those added by a single circuit
384 const size_t NUM_SUBTABLES = 3;
385 std::vector<size_t> subtable_op_counts = { 4, 2, 7 };
386
387 TableGenerator table_generator;
388 auto subtables = table_generator.generate_subtables(NUM_SUBTABLES, subtable_op_counts);
389
390 std::array<MergeSettings, NUM_SUBTABLES> merge_settings = { MergeSettings::PREPEND,
391 MergeSettings::PREPEND,
392 MergeSettings::APPEND };
393 // Construct the concatenated eccvm ops table
394 EccvmOpsTable eccvm_ops_table;
395 for (const auto& [subtable_ops, setting] : zip_view(subtables, merge_settings)) {
396 eccvm_ops_table.create_new_subtable();
397 for (const auto& op : subtable_ops) {
398 eccvm_ops_table.push(op);
399 }
400 eccvm_ops_table.merge(setting);
401 }
402
404 for (auto [subtable, setting] : zip_view(subtables, merge_settings)) {
405 auto it = setting == MergeSettings::PREPEND ? ordered_subtables.begin() : ordered_subtables.end();
406 ordered_subtables.insert(it, subtable);
407 }
408
409 // Construct the mock ultra ops table which contains the subtables ordered in reverse (as if prepended)
410 EccOpsTableTest::MockEccvmOpsTable expected_eccvm_ops_table(ordered_subtables);
411
412 // Check that the table has the correct size
413 auto expected_num_ops = std::accumulate(subtable_op_counts.begin(), subtable_op_counts.end(), size_t(0));
414 EXPECT_EQ(eccvm_ops_table.size(), expected_num_ops);
415
416 // Check that accessing the table values via operator[] matches the manually constructed mock table
417 for (size_t i = 0; i < expected_num_ops; ++i) {
418 EXPECT_EQ(expected_eccvm_ops_table.eccvm_ops[i], eccvm_ops_table[i]);
419 }
420
421 // Check that the copy-based reconstruction of the eccvm ops table matches the expected table
422 EXPECT_EQ(expected_eccvm_ops_table.eccvm_ops, eccvm_ops_table.get_reconstructed());
423}
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:59
#define ASSERT(expression,...)
Definition assert.hpp:49
size_t size() const
void create_new_subtable(size_t size_hint=0)
std::vector< OpFormat > get_reconstructed() const
void merge(MergeSettings settings=MergeSettings::PREPEND)
void push(const OpFormat &op)
Stores a table of elliptic curve operations represented in the Ultra format.
void push(const UltraOp &op)
ColumnPolynomials construct_table_columns() const
static constexpr size_t NUM_ROWS_PER_OP
void create_new_subtable(size_t size_hint=0)
size_t ultra_table_size() const
void merge(MergeSettings settings=MergeSettings::PREPEND, std::optional< size_t > offset=std::nullopt)
Entry point for Barretenberg command-line interface.
TEST(MegaCircuitBuilder, CopyConstructor)
field< Bn254FrParams > fr
Definition fr.hpp:174
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
ECCVMOperation generate_random_op() const override
MockEccvmOpsTable(const auto &subtable_ops)
std::vector< ECCVMOperation > eccvm_ops
std::vector< std::vector< Op > > generate_subtables(size_t num_subtables, std::vector< size_t > ops_per_table)
virtual Op generate_random_op() const =0
std::array< std::vector< Scalar >, 4 > columns
MockUltraOpsTable(const auto &subtable_ops)
Defines the opcodes for ECC operations used in both the Ultra and ECCVM formats. There are three opco...
uint32_t value() const
EccOpCode op_code
static field random_element(numeric::RNG *engine=nullptr) noexcept
static constexpr field zero()