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: Complete, auditors: [Raju], commit: }
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
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 // encodes add*8 + mul*4 + eq*2 + reset*1
50 [[nodiscard]] uint32_t value() const
51 {
52 if (is_random_op) {
53 throw_or_abort("EccOpCode::value() should not be called on a random op");
54 }
55 auto res = static_cast<uint32_t>(add);
56 res += res;
57 res += static_cast<uint32_t>(mul);
58 res += res;
59 res += static_cast<uint32_t>(eq);
60 res += res;
61 res += static_cast<uint32_t>(reset);
62 return res;
63 }
64};
65
66struct UltraOp {
77
78 bool operator==(const UltraOp& other) const = default;
79
86 {
88 return { Fq(0), Fq(0) };
89 }
90 auto x = Fq((uint256_t(x_hi) << 2 * stdlib::NUM_LIMB_BITS_IN_FIELD_SIMULATION) + uint256_t(x_lo));
91 auto y = Fq((uint256_t(y_hi) << 2 * stdlib::NUM_LIMB_BITS_IN_FIELD_SIMULATION) + uint256_t(y_lo));
92
93 return { x, y };
94 }
95};
96
99 using AffineElement = Curve::Group::affine_element;
106 bool operator==(const ECCVMOperation& other) const = default;
107};
108
118template <typename OpFormat> class EccOpsTable {
119 using Subtable = std::vector<OpFormat>;
120 std::deque<Subtable> table;
121 Subtable current_subtable; // used to store the current subtable before it is added to the table
122 public:
123 size_t size() const
124 {
126 "Current subtable should be merged before computing the size of the full table of ecc ops.");
127 size_t total = 0;
128 for (const auto& subtable : table) {
129 total += subtable.size();
130 }
131
132 return total;
133 }
134
135 size_t num_subtables() const { return table.size(); }
136 size_t get_current_subtable_size() const { return current_subtable.size(); }
137
138 auto& get() const { return table; }
139
140 void push(const OpFormat& op) { current_subtable.push_back(op); }
141
143 {
144 BB_ASSERT(current_subtable.empty(), "Cannot create a new subtable until the current subtable has been merged.");
146 }
147
148 // const operator[]. (there is no non-const version.)
149 const OpFormat& operator[](size_t index) const
150 {
152 "Current subtable should be merged before attempting to index into the full table.");
154 // simple linear search to find the correct subtable
155 for (const auto& subtable : table) {
156 if (index < subtable.size()) {
157 return subtable[index]; // found the correct subtable
158 }
159 index -= subtable.size(); // move to the next subtable
160 }
161 BB_ASSERT(
162 false,
163 "Unreachable: something has gone wrong with the subtable sizes, which do not add up to the table size.");
164 // Unreachable
165 return table.front().front();
166 }
167
168 // highly inefficient copy-based reconstruction of the table for use in ECCVM/Translator. Used once at the end of an
169 // IVC.
170 std::vector<OpFormat> get_reconstructed() const
171 {
173 "current subtable should be merged before reconstructing the full table of operations.");
174
175 std::vector<OpFormat> reconstructed_table;
176 reconstructed_table.reserve(size());
177 for (const auto& subtable : table) {
178 for (const auto& op : subtable) {
179 reconstructed_table.push_back(op);
180 }
181 }
182 return reconstructed_table;
183 }
184
186 {
187 if (current_subtable.empty()) {
188 return; // nothing to merge
189 }
190
191 // Based on merge settings add the current subtable to either the beginning or end of the full table
192 if (settings == MergeSettings::PREPEND) {
193 table.push_front(std::move(current_subtable));
194 } else {
195 table.push_back(std::move(current_subtable));
196 }
197
198 current_subtable.clear(); // clear the current subtable after merging
199 BB_ASSERT(current_subtable.empty(), "current subtable should be empty after merging. Check the merge logic.");
200 }
201};
202
208
223 public:
224 static constexpr size_t TABLE_WIDTH = 4; // dictated by the number of wires in the Ultra arithmetization
225 static constexpr size_t NUM_ROWS_PER_OP = 2; // A single ECC op is split across two width-4 rows
226
227 private:
232
233 size_t current_subtable_idx = 0; // index of the current subtable being constructed
235
236 // For fixed-location append functionality
237 // APPEND mode is only used by the hiding kernel to place its ops at t fixed position at the end of the table,
238 // ensuring constant total table size for zero-knowledge. See chonk/README.md "Constant Merged Table Size for ZK"
239 // for details. (Only applicable for ultra ops.)
241 bool has_fixed_append = false;
242
243 public:
244 // Returns the number of ECC operations in the table
245 size_t num_ops() const { return table.size(); }
246
247 // Returns the number of rows in the Ultra execution trace (each op occupies NUM_ROWS_PER_OP rows)
248 size_t num_ultra_rows() const
249 {
250 size_t base_size = table.size() * NUM_ROWS_PER_OP;
251 if (has_fixed_append && fixed_append_offset.has_value()) {
252 // Include zeros gap and final subtable at fixed location
253 size_t last_subtable_size = 0;
254 if (!table.get().empty()) {
255 // The last subtable in deque is the fixed-location one
256 last_subtable_size = table.get().back().size() * NUM_ROWS_PER_OP;
257 }
258 return std::max(base_size, (fixed_append_offset.value() * NUM_ROWS_PER_OP) + last_subtable_size);
259 }
260 return base_size;
261 }
264 void create_new_subtable(size_t size_hint = 0) { table.create_new_subtable(size_hint); }
265 void push(const UltraOp& op) { table.push(op); }
267 {
268 if (settings == MergeSettings::APPEND) {
269 // All appends are treated as fixed-location for ultra ops
270 BB_ASSERT(!has_fixed_append, "Can only perform fixed-location append once");
271 // Set fixed location at which to append ultra ops. If nullopt --> append right after prepended tables
273 has_fixed_append = true;
274 table.merge(settings);
276 } else { // MergeSettings::PREPEND
277 table.merge(settings);
279 }
280 }
281
283
284 std::vector<UltraOp> get_reconstructed() const
285 {
286 if (has_fixed_append && fixed_append_offset.has_value()) {
288 }
289 return table.get_reconstructed();
290 }
291 std::vector<UltraOp> get_reconstructed_with_fixed_append() const
292 {
293
295 "current subtable should be merged before reconstructing the full table of operations.");
296
297 std::vector<UltraOp> reconstructed_table;
298 reconstructed_table.reserve(1 << CONST_OP_QUEUE_LOG_SIZE);
299
300 for (size_t subtable_idx = 0; subtable_idx < table.num_subtables() - 1; subtable_idx++) {
301 const auto& subtable = table.get()[subtable_idx];
302 for (const auto& op : subtable) {
303 reconstructed_table.push_back(op);
304 }
305 }
306
307 // Add zeros if fixed offset is larger than current size
308 if (has_fixed_append && fixed_append_offset.has_value()) {
309 size_t current_size = reconstructed_table.size();
310 size_t target_offset = fixed_append_offset.value();
311 BB_ASSERT_LTE(current_size, target_offset, "Current table size is larger than fixed append offset.");
312
313 // Fill gap with no-ops if needed
314 reconstructed_table.insert(reconstructed_table.end(), target_offset - current_size, UltraOp{ /*no-op*/ });
315 }
316
317 // Add the final subtable (appended at fixed location)
318 const auto& final_subtable = table.get()[table.num_subtables() - 1];
319 for (const auto& op : final_subtable) {
320 reconstructed_table.push_back(op);
321 }
322 return reconstructed_table;
323 }
324
325 // Construct the columns of the full ultra ecc ops table
327 {
328 const size_t poly_size = num_ultra_rows();
329
330 if (has_fixed_append) {
331 // Handle fixed-location append: prepended tables first, then appended table at fixed offset
333 }
334
335 // Normal case: all subtables in order
336 const size_t subtable_start_idx = 0; // include all subtables
337 const size_t subtable_end_idx = table.num_subtables();
338 return construct_column_polynomials_from_subtables(poly_size, subtable_start_idx, subtable_end_idx);
339 }
340
341 // Construct the columns of the previous full ultra ecc ops table
343 {
344 const size_t poly_size = previous_ultra_table_size();
345 const size_t subtable_start_idx = current_subtable_idx == 0 ? 1 : 0;
346 const size_t subtable_end_idx = current_subtable_idx == 0 ? table.num_subtables() : table.num_subtables() - 1;
347
348 return construct_column_polynomials_from_subtables(poly_size, subtable_start_idx, subtable_end_idx);
349 }
350
351 // Construct the columns of the current ultra ecc ops subtable which is either the first or the last one
352 // depening on whether it has been prepended or appended
354 {
355 const size_t poly_size = current_ultra_subtable_size();
356 const size_t subtable_start_idx = current_subtable_idx;
357 const size_t subtable_end_idx = current_subtable_idx + 1;
358
359 return construct_column_polynomials_from_subtables(poly_size, subtable_start_idx, subtable_end_idx);
360 }
361
362 private:
370 static void write_op_to_polynomials(ColumnPolynomials& column_polynomials, const UltraOp& op, const size_t row_idx)
371 {
372 column_polynomials[0].at(row_idx) = !op.op_code.is_random_op ? op.op_code.value() : op.op_code.random_value_1;
373 column_polynomials[1].at(row_idx) = op.x_lo;
374 column_polynomials[2].at(row_idx) = op.x_hi;
375 column_polynomials[3].at(row_idx) = op.y_lo;
376 column_polynomials[0].at(row_idx + 1) = !op.op_code.is_random_op ? 0 : op.op_code.random_value_2;
377 column_polynomials[1].at(row_idx + 1) = op.y_hi;
378 column_polynomials[2].at(row_idx + 1) = op.z_1;
379 column_polynomials[3].at(row_idx + 1) = op.z_2;
380 }
381
387 {
388 ColumnPolynomials column_polynomials;
389 for (auto& poly : column_polynomials) {
390 poly = Polynomial<Fr>(poly_size); // Initialized to zeros
391 }
392
393 // Process all prepended subtables (all except last)
394 size_t i = 0;
395 for (size_t subtable_idx = 0; subtable_idx < table.num_subtables() - 1; subtable_idx++) {
396 const auto& subtable = table.get()[subtable_idx];
397 for (const auto& op : subtable) {
398 write_op_to_polynomials(column_polynomials, op, i);
399 i += NUM_ROWS_PER_OP;
400 }
401 }
402
403 // Place the appended subtable at the fixed offset
404 size_t append_position = fixed_append_offset.has_value() ? fixed_append_offset.value() * NUM_ROWS_PER_OP : i;
405 const auto& appended_subtable = table.get()[table.num_subtables() - 1];
406
407 size_t j = append_position;
408 for (const auto& op : appended_subtable) {
409 write_op_to_polynomials(column_polynomials, op, j);
410 j += NUM_ROWS_PER_OP;
411 }
412
413 // Any gap between prepended tables and appended table remains zeros (from initialization)
414 return column_polynomials;
415 }
416
423 const size_t subtable_start_idx,
424 const size_t subtable_end_idx) const
425 {
426 ColumnPolynomials column_polynomials;
427 for (auto& poly : column_polynomials) {
428 poly = Polynomial<Fr>(poly_size);
429 }
430
431 size_t i = 0;
432 for (size_t subtable_idx = subtable_start_idx; subtable_idx < subtable_end_idx; ++subtable_idx) {
433 const auto& subtable = table.get()[subtable_idx];
434 for (const auto& op : subtable) {
435 write_op_to_polynomials(column_polynomials, op, i);
436 i += NUM_ROWS_PER_OP;
437 }
438 }
439 return column_polynomials;
440 }
441};
442
443} // namespace bb
#define BB_ASSERT(expression,...)
Definition assert.hpp:80
#define BB_ASSERT_LTE(left, right,...)
Definition assert.hpp:168
#define BB_ASSERT_LT(left, right,...)
Definition assert.hpp:153
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)
size_t num_ultra_rows() const
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
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
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.
Definition api.hpp:5
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)