Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
ecc_op_queue.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
14namespace bb {
15
42 using Fq = Curve::BaseField; // Grumpkin's scalar field
44 Point point_at_infinity = Curve::Group::affine_point_at_infinity;
45
46 // The operations written to the queue are also performed natively; the result is stored in accumulator
48
49 EccvmOpsTable eccvm_ops_table; // table of ops in the ECCVM format
50 UltraEccOpsTable ultra_ops_table; // table of ops in the Ultra-arithmetization format
51
52 // Storage for the reconstructed eccvm ops table in contiguous memory. (Intended to be constructed once and for all
53 // prior to ECCVM construction to avoid repeated prepending of subtables in physical memory).
54 std::vector<ECCVMOperation> eccvm_ops_reconstructed;
55
56 // Storage for the reconstructed ultra ops table in contiguous memory. (Intended to be constructed once and for all
57 // prior to Translator circuit construction to avoid repeated prepending of subtables in physical memory).
58 std::vector<UltraOp> ultra_ops_reconstructed;
59
60 // Tracks number of muls and size of eccvm in real time as the op queue is updated
62
63 public:
64 static const size_t OP_QUEUE_SIZE = 1 << CONST_OP_QUEUE_LOG_SIZE;
69
79
81
83 {
84 eccvm_ops_table.merge(settings);
85 ultra_ops_table.merge(settings, ultra_fixed_offset);
86 }
87
88 // Construct polynomials corresponding to the columns of the full aggregate ultra ecc ops table
93
94 // Construct polys corresponding to the columns of the aggregate ultra ops table, excluding the most recent
95 // subtable
100
101 // Construct polynomials corresponding to the columns of the current subtable of ultra ecc ops
106
107 // Reconstruct the full table of eccvm ops in contiguous memory from the independent subtables
109
110 // Reconstruct the full table of ultra ops in contiguous memory from the independent subtables
112
114 size_t get_ultra_ops_count() const { return ultra_ops_table.num_ops(); } // actual operation count without padding
117
118 // TODO(https://github.com/AztecProtocol/barretenberg/issues/1339): Consider making the ultra and eccvm ops
119 // getters more memory efficient
120
121 // Get the full table of ECCVM ops in contiguous memory; construct it if it has not been constructed already.
122 // The hiding op (set via append_hiding_op) is always prepended at index 0.
123 std::vector<ECCVMOperation>& get_eccvm_ops()
124 {
125 if (eccvm_ops_reconstructed.empty()) {
127 // Prepend the hiding op at index 0 (required for ZK)
128 if (!has_hiding_op) {
129 throw_or_abort("Hiding op must be set before calling get_eccvm_ops()");
130 }
132 }
134 }
135
136 std::vector<UltraOp>& get_ultra_ops()
137 {
138 if (ultra_ops_reconstructed.empty()) {
140 }
142 }
143
148
153 size_t get_num_rows() const { return eccvm_row_tracker.get_num_rows(); }
154
159
164 void set_eccvm_ops_for_fuzzing(std::vector<ECCVMOperation>& eccvm_ops_in)
165 {
166 eccvm_ops_reconstructed = eccvm_ops_in;
167 }
168
175 {
176 EccOpCode op_code{ .eq = true, .reset = true };
177 append_eccvm_op(ECCVMOperation{ .op_code = op_code, .base_point = Point::random_element() });
178 }
179
186 {
188 accumulator.self_set_infinity();
189 }
190
192
199 {
200 // Update the accumulator natively
201 accumulator = accumulator + to_add;
202 EccOpCode op_code{ .add = true };
203 // Store the eccvm operation
204 append_eccvm_op(ECCVMOperation{ .op_code = op_code, .base_point = to_add });
205
206 // Construct and store the operation in the ultra op format
207 return construct_and_populate_ultra_ops(op_code, to_add);
208 }
209
215 UltraOp mul_accumulate(const Point& to_mul, const Fr& scalar)
216 {
217 // Update the accumulator natively
218 accumulator = accumulator + to_mul * scalar;
219 EccOpCode op_code{ .mul = true };
220
221 // Construct and store the operation in the ultra op format
222 UltraOp ultra_op = construct_and_populate_ultra_ops(op_code, to_mul, scalar);
223
224 // Store the eccvm operation
226 .op_code = op_code,
227 .base_point = to_mul,
228 .z1 = ultra_op.z_1,
229 .z2 = ultra_op.z_2,
230 .mul_scalar_full = scalar,
231 });
232
233 return ultra_op;
234 }
235
243 {
244 UltraOp no_op{};
245 ultra_ops_table.push(no_op);
246 return no_op;
247 }
248
257 {
258 UltraOp random_op{ .op_code = EccOpCode{ .is_random_op = true,
259 .random_value_1 = Fr::random_element(),
260 .random_value_2 = Fr::random_element() },
261 .x_lo = Fr::random_element(),
262 .x_hi = Fr::random_element(),
263 .y_lo = Fr::random_element(),
264 .y_hi = Fr::random_element(),
265 .z_1 = Fr::random_element(),
266 .z_2 = Fr::random_element(),
267 .return_is_infinity = false };
268 ultra_ops_table.push(random_op);
269 return random_op;
270 }
271
278 {
279 auto expected = accumulator;
280 accumulator.self_set_infinity();
281 EccOpCode op_code{ .eq = true, .reset = true };
282 // Store eccvm operation
283 append_eccvm_op(ECCVMOperation{ .op_code = op_code, .base_point = expected });
284
285 // Construct and store the operation in the ultra op format
286 return construct_and_populate_ultra_ops(op_code, expected);
287 }
288
314 UltraOp append_hiding_op(const Fq& Px, const Fq& Py)
315 {
316 // Create an ECCVM operation with q_eq = 1, q_reset = 1 (opcode = 3) and the random Px, Py values.
317 // We construct the base_point directly with the raw coordinates - it may not be on the curve.
318 // Note: reset = true is required for Translator compatibility (only opcodes {0,3,4,8} are allowed)
319 EccOpCode op_code{ .eq = true, .reset = true }; // q_eq = 1, q_reset = 1
320 Point base_point;
321 base_point.x = Px;
322 base_point.y = Py;
323 // Note: We don't call is_point_at_infinity() or any curve operations on this point
324
325 // Store the hiding op for ECCVM - it will be prepended to the front during reconstruction (index 0 -> row 1)
326 hiding_op_for_eccvm = ECCVMOperation{ .op_code = op_code, .base_point = base_point };
327 has_hiding_op = true;
328
329 // Push to Ultra ops through normal flow (appends to current subtable)
330 // Decompose Px, Py (Fq) into hi-lo chunks (Fr)
331 const size_t CHUNK_SIZE = 2 * stdlib::NUM_LIMB_BITS_IN_FIELD_SIMULATION;
332 uint256_t x_256(Px);
333 uint256_t y_256(Py);
334 UltraOp ultra_op{
335 .op_code = op_code,
336 .x_lo = Fr(x_256.slice(0, CHUNK_SIZE)),
337 .x_hi = Fr(x_256.slice(CHUNK_SIZE, CHUNK_SIZE * 2)),
338 .y_lo = Fr(y_256.slice(0, CHUNK_SIZE)),
339 .y_hi = Fr(y_256.slice(CHUNK_SIZE, CHUNK_SIZE * 2)),
340 .z_1 = Fr(0),
341 .z_2 = Fr(0),
342 .return_is_infinity = false,
343 };
344 ultra_ops_table.push(ultra_op);
345
346 // Do NOT update the accumulator - the hiding op doesn't perform any actual EC computation
347 return ultra_op;
348 }
349
350 private:
351 // === Hiding Op State ===
352 // The hiding op is handled asymmetrically but ends up at the same functional relative position in both:
353 // - ECCVM: Stored here and prepended at index 0 during get_eccvm_ops() reconstruction
354 // - Ultra: Pushed to ultra_ops_table at index 4 (after 1 no-op + 3 random padding ops)
355 //
356 // Both end up with hiding op as the first "real" op because:
357 // - ECCVM: prepending puts it at index 0; padding ops don't exist in ECCVM table
358 // - Translator: skips first 4 Ultra ops (padding), so accumulation starts at the hiding op
359 //
360 // This alignment is required for the translation check (ECCVM and Translator must compute
361 // the same accumulated_result). ECCVM places it at row 1 (lagrange_second) where on-curve
362 // and eq constraints are gated off, allowing non-curve (x, y) values.
364 bool has_hiding_op = false;
365
383 UltraOp construct_and_populate_ultra_ops(EccOpCode op_code, const Point& point, const Fr& scalar = Fr::zero())
384 {
385 UltraOp ultra_op;
386 ultra_op.op_code = op_code;
387
388 // Decompose point coordinates (Fq) into hi-lo chunks (Fr)
389 const size_t CHUNK_SIZE = 2 * stdlib::NUM_LIMB_BITS_IN_FIELD_SIMULATION;
390 uint256_t x_256(point.x);
391 uint256_t y_256(point.y);
392 ultra_op.return_is_infinity = point.is_point_at_infinity();
393 // if we have a point at infinity, set x/y to zero
394 // in the biggroup_goblin class we use `assert_equal` statements to validate
395 // the original in-circuit coordinate values are also zero
396 if (point.is_point_at_infinity()) {
397 x_256 = 0;
398 y_256 = 0;
399 }
400 ultra_op.x_lo = Fr(x_256.slice(0, CHUNK_SIZE));
401 ultra_op.x_hi = Fr(x_256.slice(CHUNK_SIZE, CHUNK_SIZE * 2));
402 ultra_op.y_lo = Fr(y_256.slice(0, CHUNK_SIZE));
403 ultra_op.y_hi = Fr(y_256.slice(CHUNK_SIZE, CHUNK_SIZE * 2));
404
405 // Split scalar into 128 bit endomorphism scalars
406 Fr z_1 = 0;
407 Fr z_2 = 0;
408 auto converted = scalar.from_montgomery_form();
409 uint256_t converted_u256(scalar);
410 // if our scalar is small, don't split.
411 if (converted_u256.get_msb() < 128) {
412 ultra_op.z_1 = scalar;
413 ultra_op.z_2 = 0;
414 } else {
415 Fr::split_into_endomorphism_scalars(converted, z_1, z_2);
416 ultra_op.z_1 = z_1.to_montgomery_form();
417 ultra_op.z_2 = z_2.to_montgomery_form();
418 }
419
420 ultra_ops_table.push(ultra_op);
421
422 return ultra_op;
423 }
424};
425
426} // namespace bb
Manages ECC operations for the Goblin proving system.
Curve::ScalarField Fr
Curve::AffineElement Point
size_t get_num_rows() const
Get the number of rows for the current ECCVM circuit.
EccvmOpsTable eccvm_ops_table
std::vector< ECCVMOperation > eccvm_ops_reconstructed
void construct_full_eccvm_ops_table()
UltraOp add_accumulate(const Point &to_add)
Write point addition op to queue and natively perform addition.
std::array< Polynomial< Fr >, ULTRA_TABLE_WIDTH > construct_ultra_ops_table_columns() const
void initialize_new_subtable()
Initialize a new subtable for eccvm and ultra ops with the given merge settings.
Point get_accumulator()
size_t get_ultra_ops_table_num_rows() const
void merge(MergeSettings settings=MergeSettings::PREPEND, std::optional< size_t > ultra_fixed_offset=std::nullopt)
void construct_full_ultra_ops_table()
UltraOp append_hiding_op(const Fq &Px, const Fq &Py)
Add a hiding op with random Px, Py values to both ECCVM and Ultra ops tables.
ECCVMOperation hiding_op_for_eccvm
UltraEccOpsTable ultra_ops_table
void append_eccvm_op(const ECCVMOperation &op)
Append an eccvm operation to the eccvm ops table; update the eccvm row tracker.
uint32_t get_number_of_muls() const
Get number of muls for the current ECCVM circuit.
std::vector< ECCVMOperation > & get_eccvm_ops()
size_t get_num_msm_rows() const
Get the number of rows in the 'msm' column section, for all msms in the circuit.
std::vector< UltraOp > ultra_ops_reconstructed
std::array< Polynomial< Fr >, ULTRA_TABLE_WIDTH > construct_current_ultra_ops_subtable_columns() const
size_t get_current_ultra_ops_subtable_num_rows() const
UltraOp construct_and_populate_ultra_ops(EccOpCode op_code, const Point &point, const Fr &scalar=Fr::zero())
Given an ecc operation and its inputs, decompose into ultra format and populate ultra_ops.
UltraOp mul_accumulate(const Point &to_mul, const Fr &scalar)
Write multiply and add op to queue and natively perform operation.
std::vector< UltraOp > & get_ultra_ops()
std::array< Polynomial< Fr >, ULTRA_TABLE_WIDTH > construct_previous_ultra_ops_table_columns() const
UltraOp random_op_ultra_only()
Writes randomness to the ultra ops table but adds no eccvm operations.
UltraOp no_op_ultra_only()
Writes a no op (i.e. two zero rows) to the ultra ops table but adds no eccvm operations.
static const size_t OP_QUEUE_SIZE
UltraOp eq_and_reset()
Write equality op using internal accumulator point.
size_t get_current_subtable_size() const
void empty_row_for_testing()
Write empty row to queue.
ECCOpQueue()
Instantiate an initial ECC op subtable.
void set_eccvm_ops_for_fuzzing(std::vector< ECCVMOperation > &eccvm_ops_in)
A fuzzing only method for setting eccvm ops directly.
EccvmRowTracker eccvm_row_tracker
size_t get_ultra_ops_count() const
void add_erroneous_equality_op_for_testing()
A testing only method that adds an erroneous equality op to the eccvm ops.
size_t get_previous_ultra_ops_table_num_rows() const
static constexpr size_t ULTRA_TABLE_WIDTH
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)
Class for tracking the number of rows in the ECCVM circuit and the number of muls performed as the op...
size_t get_num_rows() const
Get the number of rows for the current ECCVM circuit.
size_t get_num_msm_rows() const
Get the number of rows in the 'msm' column section, for all msms in the circuit.
uint32_t get_number_of_muls() const
void update_cached_msms(const ECCVMOperation &op)
Update cached_active_msm_count or update other row counts and reset cached_active_msm_count.
Stores a table of elliptic curve operations represented in the Ultra format.
size_t get_current_subtable_size() const
void push(const UltraOp &op)
size_t num_ultra_rows() const
ColumnPolynomials construct_current_ultra_ops_subtable_columns() const
ColumnPolynomials construct_table_columns() const
ColumnPolynomials construct_previous_table_columns() const
void create_new_subtable(size_t size_hint=0)
size_t current_ultra_subtable_size() const
size_t previous_ultra_table_size() const
std::vector< UltraOp > get_reconstructed() const
static constexpr size_t TABLE_WIDTH
void merge(MergeSettings settings=MergeSettings::PREPEND, std::optional< size_t > offset=std::nullopt)
bb::fq BaseField
Definition bn254.hpp:19
typename Group::affine_element AffineElement
Definition bn254.hpp:22
bb::fr ScalarField
Definition bn254.hpp:18
constexpr uint256_t slice(uint64_t start, uint64_t end) const
constexpr uint64_t get_msb() const
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
Defines the opcodes for ECC operations used in both the Ultra and ECCVM formats. There are three opco...
bool return_is_infinity
EccOpCode op_code
BB_INLINE constexpr field to_montgomery_form() const noexcept
static void split_into_endomorphism_scalars(const field &k, field &k1, field &k2)
static field random_element(numeric::RNG *engine=nullptr) noexcept
BB_INLINE constexpr field from_montgomery_form() const noexcept
static constexpr field zero()
void throw_or_abort(std::string const &err)