Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
ecc_ops_table.hpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: not started, auditors: [], date: YYYY-MM-DD }
3// external_1: { status: not started, auditors: [], date: YYYY-MM-DD }
4// external_2: { status: not started, auditors: [], date: YYYY-MM-DD }
5// =====================
6
7#pragma once
8
14#include <deque>
15namespace bb {
16
22
37struct EccOpCode {
39 bool add = false;
40 bool mul = false;
41 bool eq = false;
42 bool reset = false;
43 bool operator==(const EccOpCode& other) const = default;
44
45 bool is_random_op = false;
48
49 [[nodiscard]] uint32_t value() const
50 {
51 if (is_random_op) {
52 throw_or_abort("EccOpCode::value() should not be called on a random op");
53 }
54 auto res = static_cast<uint32_t>(add);
55 res += res;
56 res += static_cast<uint32_t>(mul);
57 res += res;
58 res += static_cast<uint32_t>(eq);
59 res += res;
60 res += static_cast<uint32_t>(reset);
61 return res;
62 }
63};
64
65struct UltraOp {
76
77 bool operator==(const UltraOp& other) const = default;
78
85 {
87 return { Fq(0), Fq(0) };
88 }
89 auto x = Fq((uint256_t(x_hi) << 2 * stdlib::NUM_LIMB_BITS_IN_FIELD_SIMULATION) + uint256_t(x_lo));
90 auto y = Fq((uint256_t(y_hi) << 2 * stdlib::NUM_LIMB_BITS_IN_FIELD_SIMULATION) + uint256_t(y_lo));
91
92 return { x, y };
93 }
94};
95
98 using AffineElement = Curve::Group::affine_element;
105 bool operator==(const ECCVMOperation& other) const = default;
106};
107
117template <typename OpFormat> class EccOpsTable {
118 using Subtable = std::vector<OpFormat>;
119 std::deque<Subtable> table;
120 Subtable current_subtable; // used to store the current subtable before it is added to the table
121 public:
122 size_t size() const
123 {
124 ASSERT(current_subtable.empty(),
125 "Current subtable should be merged before computing the size of the full table of ecc ops.");
126 size_t total = 0;
127 for (const auto& subtable : table) {
128 total += subtable.size();
129 }
130
131 return total;
132 }
133
134 size_t num_subtables() const { return table.size(); }
135 size_t get_current_subtable_size() const { return current_subtable.size(); }
136
137 auto& get() const { return table; }
138
139 void push(const OpFormat& op)
140 {
141 // Get the reference of the subtable to update
142
143 current_subtable.push_back(op);
144 }
145
146 void create_new_subtable(size_t size_hint = 0)
147 {
148 ASSERT(current_subtable.empty(), "Cannot create a new subtable until the current subtable has been merged.");
149 current_subtable.reserve(size_hint);
150 }
151
152 // const version of operator[]
153 const OpFormat& operator[](size_t index) const
154 {
155 ASSERT(current_subtable.empty(),
156 "Current subtable should be merged before attempting to index into the full table.");
157 BB_ASSERT_LT(index, size());
158 // simple linear search to find the correct subtable
159 for (const auto& subtable : table) {
160 if (index < subtable.size()) {
161 return subtable[index]; // found the correct subtable
162 }
163 index -= subtable.size(); // move to the next subtable
164 }
165 return table.front().front(); // should never reach here
166 }
167
168 // highly inefficient copy-based reconstruction of the table for use in ECCVM/Translator
169 std::vector<OpFormat> get_reconstructed() const
170 {
171 ASSERT(current_subtable.empty(),
172 "current subtable should be merged before reconstructing the full table of operations.");
173
174 std::vector<OpFormat> reconstructed_table;
175 reconstructed_table.reserve(size());
176 for (const auto& subtable : table) {
177 for (const auto& op : subtable) {
178 reconstructed_table.push_back(op);
179 }
180 }
181 return reconstructed_table;
182 }
183
185 {
186 if (current_subtable.empty()) {
187 return; // nothing to merge
188 }
189
190 // Based on merge settings add the current subtable to either the beginning or end of the full table
191 if (settings == MergeSettings::PREPEND) {
192 table.push_front(std::move(current_subtable));
193 } else {
194 table.push_back(std::move(current_subtable));
195 }
196
197 current_subtable.clear(); // clear the current subtable after merging
198 ASSERT(current_subtable.empty(), "current subtable should be empty after merging. Check the merge logic.");
199 }
200};
201
207
222 public:
223 static constexpr size_t TABLE_WIDTH = 4; // dictated by the number of wires in the Ultra arithmetization
224 static constexpr size_t NUM_ROWS_PER_OP = 2; // A single ECC op is split across two width-4 rows
225
226 private:
232
233 size_t current_subtable_idx = 0; // index of the current subtable being constructed
235
236 // For fixed-location append functionality (ultra ops only)
238 bool has_fixed_append = false;
239
240 public:
241 size_t size() const { return table.size(); }
242 size_t ultra_table_size() const
243 {
244 size_t base_size = table.size() * NUM_ROWS_PER_OP;
245 if (has_fixed_append && fixed_append_offset.has_value()) {
246 // Include zeros gap and final subtable at fixed location
247 size_t last_subtable_size = 0;
248 if (!table.get().empty()) {
249 // The last subtable in deque is the fixed-location one
250 last_subtable_size = table.get().back().size() * NUM_ROWS_PER_OP;
251 }
252 return std::max(base_size, (fixed_append_offset.value() * NUM_ROWS_PER_OP) + last_subtable_size);
253 }
254 return base_size;
255 }
258 void create_new_subtable(size_t size_hint = 0) { table.create_new_subtable(size_hint); }
259 void push(const UltraOp& op) { table.push(op); }
261 {
262 if (settings == MergeSettings::APPEND) {
263 // All appends are treated as fixed-location for ultra ops
264 ASSERT(!has_fixed_append, "Can only perform fixed-location append once");
265 // Set fixed location at which to append ultra ops. If nullopt --> append right after prepended tables
267 has_fixed_append = true;
268 table.merge(settings);
270 } else { // MergeSettings::PREPEND
271 table.merge(settings);
273 }
274 }
275
277
278 std::vector<UltraOp> get_reconstructed() const
279 {
280 if (has_fixed_append && fixed_append_offset.has_value()) {
282 }
283 return table.get_reconstructed();
284 }
285 std::vector<UltraOp> get_reconstructed_with_fixed_append() const
286 {
287
289 "current subtable should be merged before reconstructing the full table of operations.");
290
291 std::vector<UltraOp> reconstructed_table;
292 reconstructed_table.reserve(1 << CONST_OP_QUEUE_LOG_SIZE);
293
294 for (size_t subtable_idx = 0; subtable_idx < table.num_subtables() - 1; subtable_idx++) {
295 const auto& subtable = table.get()[subtable_idx];
296 for (const auto& op : subtable) {
297 reconstructed_table.push_back(op);
298 }
299 }
300
301 // Add zeros if fixed offset is larger than current size
302 if (has_fixed_append && fixed_append_offset.has_value()) {
303 size_t current_size = reconstructed_table.size();
304 size_t target_offset = fixed_append_offset.value();
305 // Fill gap with no-ops if needed
306 reconstructed_table.insert(reconstructed_table.end(), target_offset - current_size, UltraOp{ /*no-op*/ });
307 }
308
309 // Add the final subtable (appended at fixed location)
310 const auto& final_subtable = table.get()[table.num_subtables() - 1];
311 for (const auto& op : final_subtable) {
312 reconstructed_table.push_back(op);
313 }
314 return reconstructed_table;
315 }
316
317 // Construct the columns of the full ultra ecc ops table
319 {
320 const size_t poly_size = ultra_table_size();
321
322 if (has_fixed_append) {
323 // Handle fixed-location append: prepended tables first, then appended table at fixed offset
325 }
326
327 // Normal case: all subtables in order
328 const size_t subtable_start_idx = 0; // include all subtables
329 const size_t subtable_end_idx = table.num_subtables();
330 return construct_column_polynomials_from_subtables(poly_size, subtable_start_idx, subtable_end_idx);
331 }
332
333 // Construct the columns of the previous full ultra ecc ops table
335 {
336 const size_t poly_size = previous_ultra_table_size();
337 const size_t subtable_start_idx = current_subtable_idx == 0 ? 1 : 0;
338 const size_t subtable_end_idx = current_subtable_idx == 0 ? table.num_subtables() : table.num_subtables() - 1;
339
340 return construct_column_polynomials_from_subtables(poly_size, subtable_start_idx, subtable_end_idx);
341 }
342
343 // Construct the columns of the current ultra ecc ops subtable which is either the first or the last one
344 // depening on whether it has been prepended or appended
346 {
347 const size_t poly_size = current_ultra_subtable_size();
348 const size_t subtable_start_idx = current_subtable_idx;
349 const size_t subtable_end_idx = current_subtable_idx + 1;
350
351 return construct_column_polynomials_from_subtables(poly_size, subtable_start_idx, subtable_end_idx);
352 }
353
354 private:
362 static void write_op_to_polynomials(ColumnPolynomials& column_polynomials, const UltraOp& op, const size_t row_idx)
363 {
364 column_polynomials[0].at(row_idx) = !op.op_code.is_random_op ? op.op_code.value() : op.op_code.random_value_1;
365 column_polynomials[1].at(row_idx) = op.x_lo;
366 column_polynomials[2].at(row_idx) = op.x_hi;
367 column_polynomials[3].at(row_idx) = op.y_lo;
368 column_polynomials[0].at(row_idx + 1) = !op.op_code.is_random_op ? 0 : op.op_code.random_value_2;
369 column_polynomials[1].at(row_idx + 1) = op.y_hi;
370 column_polynomials[2].at(row_idx + 1) = op.z_1;
371 column_polynomials[3].at(row_idx + 1) = op.z_2;
372 }
373
379 {
380 ColumnPolynomials column_polynomials;
381 for (auto& poly : column_polynomials) {
382 poly = Polynomial<Fr>(poly_size); // Initialized to zeros
383 }
384
385 // Process all prepended subtables (all except last)
386 size_t i = 0;
387 for (size_t subtable_idx = 0; subtable_idx < table.num_subtables() - 1; subtable_idx++) {
388 const auto& subtable = table.get()[subtable_idx];
389 for (const auto& op : subtable) {
390 write_op_to_polynomials(column_polynomials, op, i);
391 i += NUM_ROWS_PER_OP;
392 }
393 }
394
395 // Place the appended subtable at the fixed offset
396 size_t append_position = fixed_append_offset.has_value() ? fixed_append_offset.value() * NUM_ROWS_PER_OP : i;
397 const auto& appended_subtable = table.get()[table.num_subtables() - 1];
398
399 size_t j = append_position;
400 for (const auto& op : appended_subtable) {
401 write_op_to_polynomials(column_polynomials, op, j);
402 j += NUM_ROWS_PER_OP;
403 }
404
405 // Any gap between prepended tables and appended table remains zeros (from initialization)
406 return column_polynomials;
407 }
408
416 const size_t subtable_start_idx,
417 const size_t subtable_end_idx) const
418 {
419 ColumnPolynomials column_polynomials;
420 for (auto& poly : column_polynomials) {
421 poly = Polynomial<Fr>(poly_size);
422 }
423
424 size_t i = 0;
425 for (size_t subtable_idx = subtable_start_idx; subtable_idx < subtable_end_idx; ++subtable_idx) {
426 const auto& subtable = table.get()[subtable_idx];
427 for (const auto& op : subtable) {
428 write_op_to_polynomials(column_polynomials, op, i);
429 i += NUM_ROWS_PER_OP;
430 }
431 }
432 return column_polynomials;
433 }
434};
435
436} // namespace bb
#define BB_ASSERT_LT(left, right,...)
Definition assert.hpp:148
#define ASSERT(expression,...)
Definition assert.hpp:77
A table of ECC operations.
std::vector< OpFormat > Subtable
size_t size() const
size_t get_current_subtable_size() const
void create_new_subtable(size_t size_hint=0)
std::deque< Subtable > table
auto & get() const
Subtable current_subtable
size_t num_subtables() const
std::vector< OpFormat > get_reconstructed() const
const OpFormat & operator[](size_t index) const
void merge(MergeSettings settings=MergeSettings::PREPEND)
void push(const OpFormat &op)
Structured polynomial class that represents the coefficients 'a' of a_0 + a_1 x .....
Stores a table of elliptic curve operations represented in the Ultra format.
size_t get_current_subtable_size() const
std::optional< size_t > fixed_append_offset
void push(const UltraOp &op)
ColumnPolynomials construct_column_polynomials_from_subtables(const size_t poly_size, const size_t subtable_start_idx, const size_t subtable_end_idx) const
Construct polynomials corresponding to the columns of the reconstructed ultra ops table for the given...
ColumnPolynomials construct_current_ultra_ops_subtable_columns() const
ColumnPolynomials construct_table_columns() const
std::array< Polynomial< Fr >, TABLE_WIDTH > ColumnPolynomials
std::array< std::span< Fr >, TABLE_WIDTH > TableView
ColumnPolynomials construct_previous_table_columns() const
static constexpr size_t NUM_ROWS_PER_OP
void create_new_subtable(size_t size_hint=0)
ColumnPolynomials construct_column_polynomials_with_fixed_append(const size_t poly_size) const
Construct polynomials with fixed-location append.
size_t current_ultra_subtable_size() const
size_t previous_ultra_table_size() const
std::vector< UltraOp > get_reconstructed() const
static void write_op_to_polynomials(ColumnPolynomials &column_polynomials, const UltraOp &op, const size_t row_idx)
Write a single UltraOp to the column polynomials at the given position.
static constexpr size_t TABLE_WIDTH
std::vector< UltraOp > get_reconstructed_with_fixed_append() const
size_t ultra_table_size() const
void merge(MergeSettings settings=MergeSettings::PREPEND, std::optional< size_t > offset=std::nullopt)
bb::fq BaseField
Definition bn254.hpp:19
bb::fr ScalarField
Definition bn254.hpp:18
ssize_t offset
Definition engine.cpp:36
Entry point for Barretenberg command-line interface.
MergeSettings
The MergeSettings define whether an current subtable will be added at the beginning (PREPEND) or at t...
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
AffineElement base_point
Curve::Group::affine_element AffineElement
bool operator==(const ECCVMOperation &other) const =default
Defines the opcodes for ECC operations used in both the Ultra and ECCVM formats. There are three opco...
bool operator==(const EccOpCode &other) const =default
uint32_t value() const
curve::BN254::ScalarField Fr
bool return_is_infinity
EccOpCode op_code
bool operator==(const UltraOp &other) const =default
std::array< Fq, 2 > get_base_point_standard_form() const
Get the point in standard form i.e. as two coordinates x and y in the base field or as a point at inf...
curve::BN254::BaseField Fq
void throw_or_abort(std::string const &err)