Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
translator_circuit_builder.cpp
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
21
22#include <cstddef>
23namespace bb {
24
38 const UltraOp& ultra_op,
39 const Fq& previous_accumulator,
40 const Fq& batching_challenge_v,
41 const Fq& evaluation_input_x)
42{
43 // All parameters are well-described in the header, this is just for convenience
44 constexpr size_t TOP_STANDARD_MICROLIMB_BITS = NUM_LAST_LIMB_BITS % MICRO_LIMB_BITS;
45 constexpr size_t TOP_Z_MICROLIMB_BITS = (NUM_Z_BITS % NUM_LIMB_BITS) % MICRO_LIMB_BITS;
46 constexpr size_t TOP_QUOTIENT_MICROLIMB_BITS =
48
56 auto uint512_t_to_limbs = [](const uint512_t& original) {
57 return std::array<Fr, NUM_BINARY_LIMBS>{ Fr(original.slice(0, NUM_LIMB_BITS).lo),
58 Fr(original.slice(NUM_LIMB_BITS, 2 * NUM_LIMB_BITS).lo),
59 Fr(original.slice(2 * NUM_LIMB_BITS, 3 * NUM_LIMB_BITS).lo),
60 Fr(original.slice(3 * NUM_LIMB_BITS, 4 * NUM_LIMB_BITS).lo) };
61 };
62
67 auto split_wide_limb_into_2_limbs = [](const Fr& wide_limb) {
69 Fr(uint256_t(wide_limb).slice(NUM_LIMB_BITS, 2 * NUM_LIMB_BITS)) };
70 };
75 auto split_standard_limb_into_micro_limbs = [](const Fr& limb) {
76 static_assert(MICRO_LIMB_BITS == 14);
85 };
86 };
87
93 auto split_top_limb_into_micro_limbs = [](const Fr& limb, const size_t last_limb_bits) {
94 static_assert(MICRO_LIMB_BITS == 14);
100 << (MICRO_LIMB_BITS - (last_limb_bits % MICRO_LIMB_BITS)),
101 0 };
102 };
103
109 auto split_top_z_limb_into_micro_limbs = [](const Fr& limb, const size_t last_limb_bits) {
110 static_assert(MICRO_LIMB_BITS == 14);
117 << (MICRO_LIMB_BITS - (last_limb_bits % MICRO_LIMB_BITS)) };
118 };
119
125 auto split_relation_limb_into_micro_limbs = [](const Fr& limb) {
126 static_assert(MICRO_LIMB_BITS == 14);
127 return std::array<Fr, 6>{
134 };
135 };
136 // x and powers of v are given to us in challenge form, so the verifier has to deal with this :)
138 Fq v_cubed = v_squared * batching_challenge_v;
139 Fq v_quarted = v_cubed * batching_challenge_v;
140
141 // Convert the accumulator, powers of v and x into "bigfield" form
142 auto previous_accumulator_limbs = split_fq_into_limbs(previous_accumulator);
143 auto v_witnesses = split_fq_into_limbs(batching_challenge_v);
144 auto v_squared_witnesses = split_fq_into_limbs(v_squared);
145 auto v_cubed_witnesses = split_fq_into_limbs(v_cubed);
146 auto v_quarted_witnesses = split_fq_into_limbs(v_quarted);
147 auto x_witnesses = split_fq_into_limbs(evaluation_input_x);
148
149 // To calculate the quotient, we need to evaluate the expression in integers. So we need uint512_t versions of all
150 // elements involved
151 size_t op_code = ultra_op.op_code.value();
152 auto uint_previous_accumulator = uint512_t(previous_accumulator);
153 auto uint_x = uint512_t(evaluation_input_x);
154 auto uint_op = uint512_t(op_code);
155 auto uint_p_x = uint512_t(uint256_t(ultra_op.x_lo) + (uint256_t(ultra_op.x_hi) << (NUM_LIMB_BITS << 1)));
156 auto uint_p_y = uint512_t(uint256_t(ultra_op.y_lo) + (uint256_t(ultra_op.y_hi) << (NUM_LIMB_BITS << 1)));
157 auto uint_z1 = uint512_t(ultra_op.z_1);
158 auto uint_z2 = uint512_t(ultra_op.z_2);
159 auto uint_v = uint512_t(batching_challenge_v);
160 auto uint_v_squared = uint512_t(v_squared);
161 auto uint_v_cubed = uint512_t(v_cubed);
162 auto uint_v_quarted = uint512_t(v_quarted);
163
164 // Construct Fq for op, P.x, P.y, z_1, z_2 for use in witness computation
165 Fq base_op = Fq(uint256_t(op_code));
166 Fq base_p_x = Fq(uint256_t(ultra_op.x_lo) + (uint256_t(ultra_op.x_hi) << (NUM_LIMB_BITS << 1)));
167 Fq base_p_y = Fq(uint256_t(ultra_op.y_lo) + (uint256_t(ultra_op.y_hi) << (NUM_LIMB_BITS << 1)));
168 Fq base_z_1 = Fq(uint256_t(ultra_op.z_1));
169 Fq base_z_2 = Fq(uint256_t(ultra_op.z_2));
170
171 // Construct bigfield representations of P.x and P.y
172 auto [p_x_0, p_x_1] = split_wide_limb_into_2_limbs(ultra_op.x_lo);
173 auto [p_x_2, p_x_3] = split_wide_limb_into_2_limbs(ultra_op.x_hi);
174 std::array<Fr, NUM_BINARY_LIMBS> p_x_limbs = { p_x_0, p_x_1, p_x_2, p_x_3 };
175 auto [p_y_0, p_y_1] = split_wide_limb_into_2_limbs(ultra_op.y_lo);
176 auto [p_y_2, p_y_3] = split_wide_limb_into_2_limbs(ultra_op.y_hi);
177 std::array<Fr, NUM_BINARY_LIMBS> p_y_limbs = { p_y_0, p_y_1, p_y_2, p_y_3 };
178
179 // Construct bigfield representations of ultra_op.z_1 and ultra_op.z_2 only using 2 limbs each
180 auto z_1_limbs = split_wide_limb_into_2_limbs(ultra_op.z_1);
181 auto z_2_limbs = split_wide_limb_into_2_limbs(ultra_op.z_2);
182
183 // The formula is `accumulator = accumulator⋅x + (op + v⋅p.x + v²⋅p.y + v³⋅z₁ + v⁴z₂)`. We need to compute the
184 // remainder (new accumulator value)
185
186 const Fq remainder = previous_accumulator * evaluation_input_x + base_z_2 * v_quarted + base_z_1 * v_cubed +
187 base_p_y * v_squared + base_p_x * batching_challenge_v + base_op;
188
189 // We also need to compute the quotient
190 uint512_t quotient_by_modulus = uint_previous_accumulator * uint_x + uint_z2 * uint_v_quarted +
191 uint_z1 * uint_v_cubed + uint_p_y * uint_v_squared + uint_p_x * uint_v + uint_op -
192 uint512_t(remainder);
193
194 uint512_t quotient = quotient_by_modulus / uint512_t(Fq::modulus);
195
196 BB_ASSERT_EQ(quotient_by_modulus, quotient * uint512_t(Fq::modulus));
197
198 // Compute quotient and remainder bigfield representation
199 auto remainder_limbs = split_fq_into_limbs(remainder);
200 std::array<Fr, NUM_BINARY_LIMBS> quotient_limbs = uint512_t_to_limbs(quotient);
201
202 // We will divide by shift_2 instantly in the relation itself, but first we need to compute the low part (0*0) and
203 // the high part (0*1, 1*0) multiplied by a single limb shift
204 Fr low_wide_relation_limb_part_1 = previous_accumulator_limbs[0] * x_witnesses[0] + op_code +
205 v_witnesses[0] * p_x_limbs[0] + v_squared_witnesses[0] * p_y_limbs[0] +
206 v_cubed_witnesses[0] * z_1_limbs[0] + v_quarted_witnesses[0] * z_2_limbs[0] +
207 quotient_limbs[0] * NEGATIVE_MODULUS_LIMBS[0] -
208 remainder_limbs[0]; // This covers the lowest limb
209
210 Fr low_wide_relation_limb =
211 low_wide_relation_limb_part_1 +
212 (previous_accumulator_limbs[1] * x_witnesses[0] + previous_accumulator_limbs[0] * x_witnesses[1] +
213 v_witnesses[1] * p_x_limbs[0] + p_x_limbs[1] * v_witnesses[0] + v_squared_witnesses[1] * p_y_limbs[0] +
214 v_squared_witnesses[0] * p_y_limbs[1] + v_cubed_witnesses[1] * z_1_limbs[0] +
215 z_1_limbs[1] * v_cubed_witnesses[0] + v_quarted_witnesses[1] * z_2_limbs[0] +
216 v_quarted_witnesses[0] * z_2_limbs[1] + quotient_limbs[0] * NEGATIVE_MODULUS_LIMBS[1] +
217 quotient_limbs[1] * NEGATIVE_MODULUS_LIMBS[0] - remainder_limbs[1]) *
218 SHIFT_1;
219
220 // Low bits have to be zero
221 BB_ASSERT_EQ(uint256_t(low_wide_relation_limb).slice(0, 2 * NUM_LIMB_BITS), 0U);
222
223 Fr low_wide_relation_limb_divided = low_wide_relation_limb * SHIFT_2_INVERSE;
224
225 // The high relation limb is the accumulation of the low limb divided by 2¹³⁶ and the combination of limbs with
226 // indices (0*2,1*1,2*0) with limbs with indices (0*3,1*2,2*1,3*0) multiplied by 2⁶⁸
227
228 Fr high_wide_relation_limb =
229 low_wide_relation_limb_divided + previous_accumulator_limbs[2] * x_witnesses[0] +
230 previous_accumulator_limbs[1] * x_witnesses[1] + previous_accumulator_limbs[0] * x_witnesses[2] +
231 v_witnesses[2] * p_x_limbs[0] + v_witnesses[1] * p_x_limbs[1] + v_witnesses[0] * p_x_limbs[2] +
232 v_squared_witnesses[2] * p_y_limbs[0] + v_squared_witnesses[1] * p_y_limbs[1] +
233 v_squared_witnesses[0] * p_y_limbs[2] + v_cubed_witnesses[2] * z_1_limbs[0] +
234 v_cubed_witnesses[1] * z_1_limbs[1] + v_quarted_witnesses[2] * z_2_limbs[0] +
235 v_quarted_witnesses[1] * z_2_limbs[1] + quotient_limbs[2] * NEGATIVE_MODULUS_LIMBS[0] +
236 quotient_limbs[1] * NEGATIVE_MODULUS_LIMBS[1] + quotient_limbs[0] * NEGATIVE_MODULUS_LIMBS[2] -
237 remainder_limbs[2] +
238 (previous_accumulator_limbs[3] * x_witnesses[0] + previous_accumulator_limbs[2] * x_witnesses[1] +
239 previous_accumulator_limbs[1] * x_witnesses[2] + previous_accumulator_limbs[0] * x_witnesses[3] +
240 v_witnesses[3] * p_x_limbs[0] + v_witnesses[2] * p_x_limbs[1] + v_witnesses[1] * p_x_limbs[2] +
241 v_witnesses[0] * p_x_limbs[3] + v_squared_witnesses[3] * p_y_limbs[0] + v_squared_witnesses[2] * p_y_limbs[1] +
242 v_squared_witnesses[1] * p_y_limbs[2] + v_squared_witnesses[0] * p_y_limbs[3] +
243 v_cubed_witnesses[3] * z_1_limbs[0] + v_cubed_witnesses[2] * z_1_limbs[1] +
244 v_quarted_witnesses[3] * z_2_limbs[0] + v_quarted_witnesses[2] * z_2_limbs[1] +
245 quotient_limbs[3] * NEGATIVE_MODULUS_LIMBS[0] + quotient_limbs[2] * NEGATIVE_MODULUS_LIMBS[1] +
246 quotient_limbs[1] * NEGATIVE_MODULUS_LIMBS[2] + quotient_limbs[0] * NEGATIVE_MODULUS_LIMBS[3] -
247 remainder_limbs[3]) *
248 SHIFT_1;
249
250 // Check that the results lower 136 bits are zero
251 BB_ASSERT_EQ(uint256_t(high_wide_relation_limb).slice(0, 2 * NUM_LIMB_BITS), 0U);
252
253 // Get divided version
254 auto high_wide_relation_limb_divided = high_wide_relation_limb * SHIFT_2_INVERSE;
255
256 const auto last_limb_index = NUM_BINARY_LIMBS - 1;
257
262 std::array<std::array<Fr, NUM_MICRO_LIMBS>, NUM_BINARY_LIMBS> current_accumulator_microlimbs;
264 // Split P_x into microlimbs for range constraining
265 for (size_t i = 0; i < last_limb_index; i++) {
266 P_x_microlimbs[i] = split_standard_limb_into_micro_limbs(p_x_limbs[i]);
267 }
268 P_x_microlimbs[last_limb_index] =
269 split_top_limb_into_micro_limbs(p_x_limbs[last_limb_index], TOP_STANDARD_MICROLIMB_BITS);
270
271 // Split P_y into microlimbs for range constraining
272 for (size_t i = 0; i < last_limb_index; i++) {
273 P_y_microlimbs[i] = split_standard_limb_into_micro_limbs(p_y_limbs[i]);
274 }
275 P_y_microlimbs[last_limb_index] =
276 split_top_limb_into_micro_limbs(p_y_limbs[last_limb_index], TOP_STANDARD_MICROLIMB_BITS);
277
278 // Split z scalars into microlimbs for range constraining
279 for (size_t i = 0; i < NUM_Z_LIMBS - 1; i++) {
280 z_1_microlimbs[i] = split_standard_limb_into_micro_limbs(z_1_limbs[i]);
281 z_2_microlimbs[i] = split_standard_limb_into_micro_limbs(z_2_limbs[i]);
282 }
283 z_1_microlimbs[NUM_Z_LIMBS - 1] =
284 split_top_z_limb_into_micro_limbs(z_1_limbs[NUM_Z_LIMBS - 1], TOP_Z_MICROLIMB_BITS);
285 z_2_microlimbs[NUM_Z_LIMBS - 1] =
286 split_top_z_limb_into_micro_limbs(z_2_limbs[NUM_Z_LIMBS - 1], TOP_Z_MICROLIMB_BITS);
287
288 // Split current accumulator into microlimbs for range constraining
289 for (size_t i = 0; i < last_limb_index; i++) {
290 current_accumulator_microlimbs[i] = split_standard_limb_into_micro_limbs(remainder_limbs[i]);
291 }
292 current_accumulator_microlimbs[last_limb_index] =
293 split_top_limb_into_micro_limbs(remainder_limbs[last_limb_index], TOP_STANDARD_MICROLIMB_BITS);
294
295 // Split quotient into microlimbs for range constraining
296 for (size_t i = 0; i < last_limb_index; i++) {
297 quotient_microlimbs[i] = split_standard_limb_into_micro_limbs(quotient_limbs[i]);
298 }
299 quotient_microlimbs[last_limb_index] =
300 split_top_limb_into_micro_limbs(quotient_limbs[last_limb_index], TOP_QUOTIENT_MICROLIMB_BITS);
301
302 // Start filling the witness container
303 AccumulationInput input{
304 .ultra_op = ultra_op,
305 .P_x_limbs = p_x_limbs,
306 .P_x_microlimbs = P_x_microlimbs,
307 .P_y_limbs = p_y_limbs,
308 .P_y_microlimbs = P_y_microlimbs,
309 .z_1_limbs = z_1_limbs,
310 .z_1_microlimbs = z_1_microlimbs,
311 .z_2_limbs = z_2_limbs,
312 .z_2_microlimbs = z_2_microlimbs,
313 .previous_accumulator = previous_accumulator_limbs,
314 .current_accumulator = remainder_limbs,
315 .current_accumulator_microlimbs = current_accumulator_microlimbs,
316 .quotient_binary_limbs = quotient_limbs,
317 .quotient_microlimbs = quotient_microlimbs,
318 .relation_wide_limbs = { low_wide_relation_limb_divided, high_wide_relation_limb_divided },
319 .relation_wide_microlimbs = { split_relation_limb_into_micro_limbs(low_wide_relation_limb_divided),
320 split_relation_limb_into_micro_limbs(high_wide_relation_limb_divided) },
321
322 };
323
324 return input;
325}
326
328{
329 // Opcode should be {0,3,4,8}
330 size_t op_code = ultra_op.op_code.value();
331 ASSERT(op_code == 0 || op_code == 3 || op_code == 4 || op_code == 8);
332
333 // Check and insert x_lo and y_hi into wire 1
336
337 // Check and insert x_hi and z_1 into wire 2
340
341 // Check and insert y_lo and z_2 into wire 3
344}
345
347{
348 // The first wires OpQueue/Transcript wires
350
351 // Check decomposition of values from the Queue into limbs used in bigfield evaluations
352 BB_ASSERT_EQ(acc_step.ultra_op.x_lo, acc_step.P_x_limbs[0] + acc_step.P_x_limbs[1] * SHIFT_1);
353 BB_ASSERT_EQ(acc_step.ultra_op.x_hi, acc_step.P_x_limbs[2] + acc_step.P_x_limbs[3] * SHIFT_1);
354 BB_ASSERT_EQ(acc_step.ultra_op.y_lo, acc_step.P_y_limbs[0] + acc_step.P_y_limbs[1] * SHIFT_1);
355 BB_ASSERT_EQ(acc_step.ultra_op.y_hi, acc_step.P_y_limbs[2] + acc_step.P_y_limbs[3] * SHIFT_1);
356 BB_ASSERT_EQ(acc_step.ultra_op.z_1, acc_step.z_1_limbs[0] + acc_step.z_1_limbs[1] * SHIFT_1);
357 BB_ASSERT_EQ(acc_step.ultra_op.z_2, acc_step.z_2_limbs[0] + acc_step.z_2_limbs[1] * SHIFT_1);
362 auto check_binary_limbs_maximum_values = []<size_t total_limbs>(const std::array<Fr, total_limbs>& limbs,
363 const uint256_t& MAX_LAST_LIMB =
365 for (size_t i = 0; i < total_limbs - 1; i++) {
366 BB_ASSERT_LT(uint256_t(limbs[i]), SHIFT_1);
367 }
368 BB_ASSERT_LT(uint256_t(limbs[total_limbs - 1]), MAX_LAST_LIMB);
369 };
370
371 const auto MAX_Z_LAST_LIMB = uint256_t(1) << (NUM_Z_BITS - NUM_LIMB_BITS);
372 const auto MAX_QUOTIENT_LAST_LIMB = uint256_t(1) << (NUM_LAST_QUOTIENT_LIMB_BITS);
373 // Check limb values are in 68-bit range
374 check_binary_limbs_maximum_values(acc_step.P_x_limbs);
375 check_binary_limbs_maximum_values(acc_step.P_y_limbs);
376 check_binary_limbs_maximum_values(acc_step.z_1_limbs, /*MAX_LAST_LIMB=*/MAX_Z_LAST_LIMB);
377 check_binary_limbs_maximum_values(acc_step.z_2_limbs, /*MAX_LAST_LIMB=*/MAX_Z_LAST_LIMB);
378 check_binary_limbs_maximum_values(acc_step.previous_accumulator);
379 check_binary_limbs_maximum_values(acc_step.current_accumulator);
380 check_binary_limbs_maximum_values(acc_step.quotient_binary_limbs, /*MAX_LAST_LIMB=*/MAX_QUOTIENT_LAST_LIMB);
381
382 // Check limbs used in range constraints are in range
383 auto check_micro_limbs_maximum_values =
384 []<size_t binary_limb_count, size_t micro_limb_count>(
385 const std::array<std::array<Fr, micro_limb_count>, binary_limb_count>& limbs) {
386 for (size_t i = 0; i < binary_limb_count; i++) {
387 for (size_t j = 0; j < micro_limb_count; j++) {
388 BB_ASSERT_LT(uint256_t(limbs[i][j]), MICRO_SHIFT);
389 }
390 }
391 };
392 check_micro_limbs_maximum_values(acc_step.P_x_microlimbs);
393 check_micro_limbs_maximum_values(acc_step.P_y_microlimbs);
394 check_micro_limbs_maximum_values(acc_step.z_1_microlimbs);
395 check_micro_limbs_maximum_values(acc_step.z_2_microlimbs);
396 check_micro_limbs_maximum_values(acc_step.current_accumulator_microlimbs);
397
398 // Check that relation limbs are in range
401}
402
404{
405 auto& op_wire = std::get<WireIds::OP>(wires);
406 if (ultra_op.op_code.is_random_op) {
407 op_wire.push_back(add_variable(ultra_op.op_code.random_value_1));
408 op_wire.push_back(add_variable(ultra_op.op_code.random_value_2));
409 } else {
410 op_wire.push_back(add_variable(ultra_op.op_code.value()));
411 // Similarly to the ColumnPolynomials in the merge protocol, the op_wire is 0 at every second index for a
412 // genuine op
413 op_wire.push_back(zero_idx());
414 }
416
418
420}
421
428{
430
432
433 // Insert limbs used in bigfield evaluations
434 insert_pair_into_wire(P_X_LOW_LIMBS, acc_step.P_x_limbs[0], acc_step.P_x_limbs[1]);
435 insert_pair_into_wire(P_X_HIGH_LIMBS, acc_step.P_x_limbs[2], acc_step.P_x_limbs[3]);
436 insert_pair_into_wire(P_Y_LOW_LIMBS, acc_step.P_y_limbs[0], acc_step.P_y_limbs[1]);
437 insert_pair_into_wire(P_Y_HIGH_LIMBS, acc_step.P_y_limbs[2], acc_step.P_y_limbs[3]);
438 insert_pair_into_wire(Z_LOW_LIMBS, acc_step.z_1_limbs[0], acc_step.z_2_limbs[0]);
439 insert_pair_into_wire(Z_HIGH_LIMBS, acc_step.z_1_limbs[1], acc_step.z_2_limbs[1]);
445
446 // We are using some leftover crevices for relation_wide_microlimbs
447 auto low_relation_microlimbs = acc_step.relation_wide_microlimbs[0];
448 auto high_relation_microlimbs = acc_step.relation_wide_microlimbs[1];
449
450 // We have 4 wires specifically for the relation microlimbs
452 RELATION_WIDE_LIMBS_RANGE_CONSTRAINT_0, low_relation_microlimbs[0], high_relation_microlimbs[0]);
454 RELATION_WIDE_LIMBS_RANGE_CONSTRAINT_1, low_relation_microlimbs[1], high_relation_microlimbs[1]);
456 RELATION_WIDE_LIMBS_RANGE_CONSTRAINT_2, low_relation_microlimbs[2], high_relation_microlimbs[2]);
458 RELATION_WIDE_LIMBS_RANGE_CONSTRAINT_3, low_relation_microlimbs[3], high_relation_microlimbs[3]);
459
460 // Next ones go into top P_x and P_y, current accumulator and quotient unused microlimbs
461
462 // Insert the second highest low relation microlimb into the space left in P_x range constraints highest wire
463 auto top_p_x_microlimbs = acc_step.P_x_microlimbs[NUM_BINARY_LIMBS - 1];
464 top_p_x_microlimbs[NUM_MICRO_LIMBS - 1] = low_relation_microlimbs[NUM_MICRO_LIMBS - 2];
465
466 // Insert the second highest high relation microlimb into the space left in P_y range constraints highest wire
467 auto top_p_y_microlimbs = acc_step.P_y_microlimbs[NUM_BINARY_LIMBS - 1];
468 top_p_y_microlimbs[NUM_MICRO_LIMBS - 1] = high_relation_microlimbs[NUM_MICRO_LIMBS - 2];
469
470 // The highest low relation microlimb goes into the crevice left in current accumulator microlimbs
471 auto top_current_accumulator_microlimbs = acc_step.current_accumulator_microlimbs[NUM_BINARY_LIMBS - 1];
472 top_current_accumulator_microlimbs[NUM_MICRO_LIMBS - 1] = low_relation_microlimbs[NUM_MICRO_LIMBS - 1];
473
474 // The highest high relation microlimb goes into the quotient crevice
475 auto top_quotient_microlimbs = acc_step.quotient_microlimbs[NUM_BINARY_LIMBS - 1];
476 top_quotient_microlimbs[NUM_MICRO_LIMBS - 1] = high_relation_microlimbs[NUM_MICRO_LIMBS - 1];
477
482 auto lay_limbs_in_row = [this]<size_t array_size>(std::array<Fr, array_size> input, WireIds starting_wire) {
483 size_t wire_index = starting_wire;
484 for (auto element : input) {
485 wires[wire_index].push_back(add_variable(element));
486 wire_index++;
487 }
488 };
489
490 // Now put all microlimbs into appropriate wires
491 lay_limbs_in_row(acc_step.P_x_microlimbs[0], P_X_LOW_LIMBS_RANGE_CONSTRAINT_0);
492 lay_limbs_in_row(acc_step.P_x_microlimbs[1], P_X_LOW_LIMBS_RANGE_CONSTRAINT_0);
493 lay_limbs_in_row(acc_step.P_x_microlimbs[2], P_X_HIGH_LIMBS_RANGE_CONSTRAINT_0);
494 lay_limbs_in_row(top_p_x_microlimbs, P_X_HIGH_LIMBS_RANGE_CONSTRAINT_0);
495 lay_limbs_in_row(acc_step.P_y_microlimbs[0], P_Y_LOW_LIMBS_RANGE_CONSTRAINT_0);
496 lay_limbs_in_row(acc_step.P_y_microlimbs[1], P_Y_LOW_LIMBS_RANGE_CONSTRAINT_0);
497 lay_limbs_in_row(acc_step.P_y_microlimbs[2], P_Y_HIGH_LIMBS_RANGE_CONSTRAINT_0);
498 lay_limbs_in_row(top_p_y_microlimbs, P_Y_HIGH_LIMBS_RANGE_CONSTRAINT_0);
499 lay_limbs_in_row(acc_step.z_1_microlimbs[0], Z_LOW_LIMBS_RANGE_CONSTRAINT_0);
500 lay_limbs_in_row(acc_step.z_2_microlimbs[0], Z_LOW_LIMBS_RANGE_CONSTRAINT_0);
501 lay_limbs_in_row(acc_step.z_1_microlimbs[1], Z_HIGH_LIMBS_RANGE_CONSTRAINT_0);
502 lay_limbs_in_row(acc_step.z_2_microlimbs[1], Z_HIGH_LIMBS_RANGE_CONSTRAINT_0);
503 lay_limbs_in_row(acc_step.current_accumulator, ACCUMULATORS_BINARY_LIMBS_0);
504 lay_limbs_in_row(acc_step.previous_accumulator, ACCUMULATORS_BINARY_LIMBS_0);
508 lay_limbs_in_row(top_current_accumulator_microlimbs, ACCUMULATOR_HIGH_LIMBS_RANGE_CONSTRAINT_0);
509 lay_limbs_in_row(acc_step.quotient_microlimbs[0], QUOTIENT_LOW_LIMBS_RANGE_CONSTRAIN_0);
510 lay_limbs_in_row(acc_step.quotient_microlimbs[1], QUOTIENT_LOW_LIMBS_RANGE_CONSTRAIN_0);
511 lay_limbs_in_row(acc_step.quotient_microlimbs[2], QUOTIENT_HIGH_LIMBS_RANGE_CONSTRAIN_0);
512 lay_limbs_in_row(top_quotient_microlimbs, QUOTIENT_HIGH_LIMBS_RANGE_CONSTRAIN_0);
513
514 num_gates += 2;
515
516 // Check that all the wires are filled equally
517 bb::constexpr_for<0, TOTAL_COUNT, 1>([&]<size_t i>() { BB_ASSERT_EQ(std::get<i>(wires).size(), num_gates); });
518}
519
521{
522 using Fq = bb::fq;
523 const auto& ultra_ops = ecc_op_queue->get_ultra_ops();
524 std::vector<Fq> accumulator_trace;
525 Fq current_accumulator(0);
526 if (ultra_ops.empty()) {
527 return;
528 }
529
530 // Handle the initial UltraOp (a no-op) by filling the start of all other wire polynomials with zeros. This ensures
531 // all translator wire polynomials begin with 0, which is necessary for shifted polynomials in the proving system.
532 // Although only the first index needs to be zero, we add two zeros to maintain consistency since each actual
533 // UltraOp populates two polynomial indices.
534 for (auto& wire : wires) {
535 wire.push_back(zero_idx());
536 wire.push_back(zero_idx());
537 }
538 num_gates += 2;
539
540 auto process_random_op = [&](const UltraOp& ultra_op) {
541 ASSERT(ultra_op.op_code.is_random_op, "function should only be called to process a random op");
543 // Populate the other wires with zeros
544 for (size_t i = WireIds::Y_LOW_Z_2 + 1; i < wires.size(); i++) {
545 wires[i].push_back(zero_idx());
546 wires[i].push_back(zero_idx());
547 }
548 num_gates += 2;
549 };
550
551 // When encountering the random operations in the op queue, populate the op wire without creating accumulation gates
552 // These are present in the op queue at the beginning and end to ensure commitments and evaluations to op queue
553 // polynomials do not reveal information about data in the op queue
554 // The position and number of these random ops are explained in ClientIVC::hide_op_queue_content_tail_kernel and
555 // ClientIVC::hide_op_queue_content_hiding_kernel
556 for (size_t i = NUM_NO_OPS_START; i <= NUM_RANDOM_OPS_START; ++i) {
557 process_random_op(ultra_ops[i]);
558 }
559 const size_t ops_end = avm_mode ? ultra_ops.size() : ultra_ops.size() - NUM_RANDOM_OPS_END;
560 // Range of UltraOps for which we should construct accumulation gates
561 std::span ultra_ops_span(ultra_ops.begin() + static_cast<std::ptrdiff_t>(NUM_NO_OPS_START + NUM_RANDOM_OPS_START),
562 ultra_ops.begin() + static_cast<std::ptrdiff_t>(ops_end));
563
564 // Pre-compute accumulator values for each step since the circuit processes values in reverse order
565 // and requires knowledge of the previous accumulator to construct each gate. Both accumulator computation
566 // and gate creation skip the initial no-ops and also the random operations at the beginning and end of the oqueue ,
567 // as these should not influence the final accumulation result (located at index RESULT_ROW). The accumulation
568 // result is sent as part of the CIVC proof, and so we add a genuine operation with randomly generated values during
569 // CIVC execution to ensure no information about the rest of the ops is leaked.
570 // Acccumulator pre-computation is achieved by processing the queue in reverse order.
571 for (const auto& ultra_op : std::ranges::reverse_view(ultra_ops_span)) {
572 if (ultra_op.op_code.value() == 0) {
573 // Skip no-ops as they should not affect the computation of the accumulator
574 continue;
575 }
576 current_accumulator *= evaluation_input_x;
577 const auto [x_256, y_256] = ultra_op.get_base_point_standard_form();
578 current_accumulator +=
579 Fq(ultra_op.op_code.value()) +
581 (x_256 + batching_challenge_v *
582 (y_256 + batching_challenge_v *
583 (uint256_t(ultra_op.z_1) + batching_challenge_v * uint256_t(ultra_op.z_2))));
584 accumulator_trace.push_back(current_accumulator);
585 }
586
587 // Accumulator final value, recomputed during witness generation and expected at RESULT_ROW
588 Fq final_accumulator_state = accumulator_trace.back();
589 accumulator_trace.pop_back();
590
591 std::array<Fr, NUM_BINARY_LIMBS> previous_accumulator_binary_limbs = split_fq_into_limbs(final_accumulator_state);
592 // Generate witness values and accumulation gates from all the actual UltraOps, starting from beginning
593 for (const auto& ultra_op : ultra_ops_span) {
594 if (ultra_op.op_code.value() == 0) {
595 // For no-op operations, the translator trace remains empty except for accumulator binary limbs,
596 // which are copied from the previous row where a real operation occurred (i.e., where the op wire
597 // at an even index contains a non-zero value). During proving, we verify that the wires at that
598 // previous row are well-formed and that the accumulator value is correctly propagated throughout
599 // the entire no-op range for both even and odd indices.
600 for (size_t j = 0; j < ACCUMULATORS_BINARY_LIMBS_0; j++) {
601 wires[j].push_back(zero_idx());
602 wires[j].push_back(zero_idx());
603 }
604 size_t idx = 0;
605 for (size_t j = ACCUMULATORS_BINARY_LIMBS_0; j < ACCUMULATORS_BINARY_LIMBS_3 + 1; j++) {
606 wires[j].push_back(add_variable(previous_accumulator_binary_limbs[idx]));
607 wires[j].push_back(add_variable(previous_accumulator_binary_limbs[idx]));
608 idx++;
609 }
610 for (size_t j = ACCUMULATORS_BINARY_LIMBS_3 + 1; j < TOTAL_COUNT; j++) {
611 wires[j].push_back(zero_idx());
612 wires[j].push_back(zero_idx());
613 }
614 num_gates += 2;
615 continue;
616 }
617 Fq previous_accumulator{ 0 };
618 // Pop the last value from accumulator trace and use it as previous accumulator
619 if (!accumulator_trace.empty()) {
620 previous_accumulator = accumulator_trace.back();
621 accumulator_trace.pop_back();
622 }
623 // Compute witness values
624 AccumulationInput one_accumulation_step =
625 generate_witness_values(ultra_op, previous_accumulator, batching_challenge_v, evaluation_input_x);
626
627 // Save the state of accumulator in case the next operation encountered is a no-op
628 previous_accumulator_binary_limbs = one_accumulation_step.previous_accumulator;
629 // And put them into the wires
630 create_accumulation_gate(one_accumulation_step);
631 }
632 // Also process the last two random ops present at the end of the op queue to hide the ecc ops of the last circuit
633 // whose ops are added to the op queue
634 for (size_t i = ops_end; i < ultra_ops.size(); ++i) {
635 process_random_op(ultra_ops[i]);
636 }
637}
638} // namespace bb
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:88
#define BB_ASSERT_LTE(left, right,...)
Definition assert.hpp:163
#define BB_ASSERT_LT(left, right,...)
Definition assert.hpp:148
#define ASSERT(expression,...)
Definition assert.hpp:77
virtual uint32_t add_variable(const FF &in)
static constexpr std::array< Fr, 5 > NEGATIVE_MODULUS_LIMBS
static AccumulationInput generate_witness_values(const UltraOp &ultra_op, const Fq &previous_accumulator, const Fq &batching_challenge_v, const Fq &evaluation_input_x)
Given the transcript values from the EccOpQueue, the values of the previous accumulator,...
void feed_ecc_op_queue_into_circuit(const std::shared_ptr< ECCOpQueue > &ecc_op_queue)
Generate all the gates required to prove the correctness of batched evalution of column polynomials r...
static void assert_well_formed_ultra_op(const UltraOp &ultra_op)
void create_accumulation_gate(const AccumulationInput &acc_step)
Create a single accumulation gate.
static std::array< Fr, NUM_BINARY_LIMBS > split_fq_into_limbs(const Fq &base)
A small function to transform a native element Fq into its bigfield representation in Fr scalars.
static constexpr size_t NUM_LAST_QUOTIENT_LIMB_BITS
static constexpr uint256_t MAX_RELATION_WIDE_LIMB_SIZE
std::array< SlabVector< uint32_t >, NUM_WIRES > wires
void populate_wires_from_ultra_op(const UltraOp &ultra_op)
static void assert_well_formed_accumulation_input(const AccumulationInput &acc_step)
Ensures the accumulation input is well-formed and can be used to create a gate.
WireIds
There are so many wires that naming them has no sense, it is easier to access them with enums.
void insert_pair_into_wire(WireIds wire_index, Fr first, Fr second)
constexpr uint256_t slice(uint64_t start, uint64_t end) const
uintx< uint256_t > uint512_t
Definition uintx.hpp:307
Entry point for Barretenberg command-line interface.
field< Bn254FqParams > fq
Definition fq.hpp:169
C slice(C const &container, size_t start)
Definition container.hpp:9
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
uint32_t value() const
The accumulation input structure contains all the necessary values to initalize an accumulation gate ...
std::array< std::array< Fr, NUM_MICRO_LIMBS >, NUM_Z_LIMBS > z_1_microlimbs
std::array< std::array< Fr, NUM_MICRO_LIMBS >, NUM_BINARY_LIMBS > P_y_microlimbs
std::array< std::array< Fr, NUM_MICRO_LIMBS >, NUM_BINARY_LIMBS > current_accumulator_microlimbs
std::array< std::array< Fr, NUM_MICRO_LIMBS >, 2 > relation_wide_microlimbs
std::array< std::array< Fr, NUM_MICRO_LIMBS >, NUM_BINARY_LIMBS > P_x_microlimbs
std::array< std::array< Fr, NUM_MICRO_LIMBS >, NUM_BINARY_LIMBS > quotient_microlimbs
std::array< std::array< Fr, NUM_MICRO_LIMBS >, NUM_Z_LIMBS > z_2_microlimbs
std::array< Fr, NUM_RELATION_WIDE_LIMBS > relation_wide_limbs
EccOpCode op_code
static constexpr uint256_t modulus