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: Planned, auditors: [], commit: }
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
6
22
23#include <cstddef>
24namespace bb {
25
39 const UltraOp& ultra_op,
40 const Fq& previous_accumulator,
41 const Fq& batching_challenge_v,
42 const Fq& evaluation_input_x)
43{
44
45 // x and v are challenges: random values unknown at compile time, treated as witnesses.
47 Fq v_cubed = v_squared * batching_challenge_v;
48 Fq v_quarted = v_cubed * batching_challenge_v;
49
50 // Convert the accumulator, powers of v and x into "bigfield" form
51 auto previous_accumulator_limbs = split_fq_into_limbs(previous_accumulator);
52 auto v_witnesses = split_fq_into_limbs(batching_challenge_v);
53 auto v_squared_witnesses = split_fq_into_limbs(v_squared);
54 auto v_cubed_witnesses = split_fq_into_limbs(v_cubed);
55 auto v_quarted_witnesses = split_fq_into_limbs(v_quarted);
56 auto x_witnesses = split_fq_into_limbs(evaluation_input_x);
57
58 // To calculate the quotient, we need to evaluate the expression in integers. So we need uint512_t versions of all
59 // elements involved
60 size_t op_code = ultra_op.op_code.value();
61 auto uint_previous_accumulator = uint512_t(previous_accumulator);
62 auto uint_x = uint512_t(evaluation_input_x);
63 auto uint_op = uint512_t(op_code);
64 // Use get_base_point_standard_form() so infinity points are consistently mapped to (0,0)
65 const auto [base_p_x, base_p_y] = ultra_op.get_base_point_standard_form();
66 auto uint_p_x = uint512_t(base_p_x);
67 auto uint_p_y = uint512_t(base_p_y);
68 auto uint_z1 = uint512_t(ultra_op.z_1);
69 auto uint_z2 = uint512_t(ultra_op.z_2);
70 auto uint_v = uint512_t(batching_challenge_v);
71 auto uint_v_squared = uint512_t(v_squared);
72 auto uint_v_cubed = uint512_t(v_cubed);
73 auto uint_v_quarted = uint512_t(v_quarted);
74
75 Fq base_op = Fq(uint256_t(op_code));
76 Fq base_z_1 = Fq(uint256_t(ultra_op.z_1));
77 Fq base_z_2 = Fq(uint256_t(ultra_op.z_2));
78
79 // Construct bigfield representations of P.x and P.y from the infinity-safe coordinates
80 auto p_x_limbs = split_fq_into_limbs(base_p_x);
81 auto p_y_limbs = split_fq_into_limbs(base_p_y);
82
83 // Construct bigfield representations of ultra_op.z_1 and ultra_op.z_2 only using 2 limbs each
84 auto z_1_limbs = split_wide_limb_into_2_limbs(ultra_op.z_1);
85 auto z_2_limbs = split_wide_limb_into_2_limbs(ultra_op.z_2);
86
87 // The formula is `accumulator = accumulator⋅x + (op + v⋅p.x + v²⋅p.y + v³⋅z₁ + v⁴z₂)`. We need to compute the
88 // remainder (new accumulator value)
89 // clang-format off
90 const Fq remainder = previous_accumulator * evaluation_input_x +
91 base_op +
92 base_p_x * batching_challenge_v +
93 base_p_y * v_squared +
94 base_z_1 * v_cubed +
95 base_z_2 * v_quarted;
96
97 // We also need to compute the quotient
98 uint512_t quotient_by_modulus = uint_previous_accumulator * uint_x +
99 uint_op +
100 uint_p_x * uint_v +
101 uint_p_y * uint_v_squared +
102 uint_z1 * uint_v_cubed +
103 uint_z2 * uint_v_quarted -
104 uint512_t(remainder);
105 // clang-format on
106
107 uint512_t quotient = quotient_by_modulus / uint512_t(Fq::modulus);
108
109 BB_ASSERT_EQ(quotient_by_modulus, quotient * uint512_t(Fq::modulus));
110
111 // Compute quotient and remainder bigfield representation
112 auto remainder_limbs = split_fq_into_limbs(remainder);
113 std::array<Fr, NUM_BINARY_LIMBS> quotient_limbs = uint512_t_to_limbs(quotient);
114
115 // We will divide by shift_2 instantly in the relation itself, but first we need to compute the low part (0*0) and
116 // the high part (0*1, 1*0) multiplied by a single limb shift
117 // clang-format off
118 Fr low_wide_relation_limb_part_1 =
119 previous_accumulator_limbs[0] * x_witnesses[0] +
120 op_code +
121 p_x_limbs[0] * v_witnesses[0] +
122 p_y_limbs[0] * v_squared_witnesses[0] +
123 z_1_limbs[0] * v_cubed_witnesses[0] +
124 z_2_limbs[0] * v_quarted_witnesses[0] +
125 quotient_limbs[0] * NEGATIVE_MODULUS_LIMBS[0] -
126 remainder_limbs[0];
127
128 Fr low_wide_relation_limb =
129 low_wide_relation_limb_part_1 +
130 (previous_accumulator_limbs[1] * x_witnesses[0] +
131 previous_accumulator_limbs[0] * x_witnesses[1] +
132 p_x_limbs[0] * v_witnesses[1] +
133 p_x_limbs[1] * v_witnesses[0] +
134 p_y_limbs[0] * v_squared_witnesses[1] +
135 p_y_limbs[1] * v_squared_witnesses[0] +
136 z_1_limbs[0] * v_cubed_witnesses[1] +
137 z_1_limbs[1] * v_cubed_witnesses[0] +
138 z_2_limbs[0] * v_quarted_witnesses[1] +
139 z_2_limbs[1] * v_quarted_witnesses[0] +
140 quotient_limbs[0] * NEGATIVE_MODULUS_LIMBS[1] +
141 quotient_limbs[1] * NEGATIVE_MODULUS_LIMBS[0] -
142 remainder_limbs[1]) * SHIFT_1;
143 // clang-format on
144
145 // Low bits have to be zero
146 BB_ASSERT_EQ(uint256_t(low_wide_relation_limb).slice(0, 2 * NUM_LIMB_BITS), 0U);
147
148 Fr low_wide_relation_limb_divided = low_wide_relation_limb * SHIFT_2_INVERSE;
149
150 // The high relation limb is the accumulation of the low limb divided by 2¹³⁶ and the combination of limbs with
151 // indices (0*2,1*1,2*0) with limbs with indices (0*3,1*2,2*1,3*0) multiplied by 2⁶⁸
152 // clang-format off
153 Fr high_wide_relation_limb_part_1 =
154 low_wide_relation_limb_divided +
155 previous_accumulator_limbs[2] * x_witnesses[0] +
156 previous_accumulator_limbs[1] * x_witnesses[1] +
157 previous_accumulator_limbs[0] * x_witnesses[2] +
158 p_x_limbs[0] * v_witnesses[2] +
159 p_x_limbs[1] * v_witnesses[1] +
160 p_x_limbs[2] * v_witnesses[0] +
161 p_y_limbs[0] * v_squared_witnesses[2] +
162 p_y_limbs[1] * v_squared_witnesses[1] +
163 p_y_limbs[2] * v_squared_witnesses[0] +
164 z_1_limbs[0] * v_cubed_witnesses[2] +
165 z_1_limbs[1] * v_cubed_witnesses[1] +
166 z_2_limbs[0] * v_quarted_witnesses[2] +
167 z_2_limbs[1] * v_quarted_witnesses[1] +
168 quotient_limbs[2] * NEGATIVE_MODULUS_LIMBS[0] +
169 quotient_limbs[1] * NEGATIVE_MODULUS_LIMBS[1] +
170 quotient_limbs[0] * NEGATIVE_MODULUS_LIMBS[2] -
171 remainder_limbs[2];
172
173 Fr high_wide_relation_limb =
174 high_wide_relation_limb_part_1 +
175 (previous_accumulator_limbs[3] * x_witnesses[0] +
176 previous_accumulator_limbs[2] * x_witnesses[1] +
177 previous_accumulator_limbs[1] * x_witnesses[2] +
178 previous_accumulator_limbs[0] * x_witnesses[3] +
179 p_x_limbs[0] * v_witnesses[3] +
180 p_x_limbs[1] * v_witnesses[2] +
181 p_x_limbs[2] * v_witnesses[1] +
182 p_x_limbs[3] * v_witnesses[0] +
183 p_y_limbs[0] * v_squared_witnesses[3] +
184 p_y_limbs[1] * v_squared_witnesses[2] +
185 p_y_limbs[2] * v_squared_witnesses[1] +
186 p_y_limbs[3] * v_squared_witnesses[0] +
187 z_1_limbs[0] * v_cubed_witnesses[3] +
188 z_1_limbs[1] * v_cubed_witnesses[2] +
189 z_2_limbs[0] * v_quarted_witnesses[3] +
190 z_2_limbs[1] * v_quarted_witnesses[2] +
191 quotient_limbs[3] * NEGATIVE_MODULUS_LIMBS[0] +
192 quotient_limbs[2] * NEGATIVE_MODULUS_LIMBS[1] +
193 quotient_limbs[1] * NEGATIVE_MODULUS_LIMBS[2] +
194 quotient_limbs[0] * NEGATIVE_MODULUS_LIMBS[3] -
195 remainder_limbs[3]) * SHIFT_1;
196 // clang-format on
197
198 // Check that the results lower 136 bits are zero
199 BB_ASSERT_EQ(uint256_t(high_wide_relation_limb).slice(0, 2 * NUM_LIMB_BITS), 0U);
200
201 // Get divided version
202 auto high_wide_relation_limb_divided = high_wide_relation_limb * SHIFT_2_INVERSE;
203
204 const auto last_limb_index = NUM_BINARY_LIMBS - 1;
205
210 std::array<std::array<Fr, NUM_MICRO_LIMBS>, NUM_BINARY_LIMBS> current_accumulator_microlimbs;
212
213 // Split all standard limbs (68-bit) into microlimbs for range constraining
214 for (size_t i = 0; i < last_limb_index; i++) {
215 P_x_microlimbs[i] = split_limb_into_microlimbs(p_x_limbs[i], NUM_LIMB_BITS);
216 P_y_microlimbs[i] = split_limb_into_microlimbs(p_y_limbs[i], NUM_LIMB_BITS);
217 current_accumulator_microlimbs[i] = split_limb_into_microlimbs(remainder_limbs[i], NUM_LIMB_BITS);
218 quotient_microlimbs[i] = split_limb_into_microlimbs(quotient_limbs[i], NUM_LIMB_BITS);
219 }
220
221 // Split top limbs (varying bit sizes) into microlimbs
222 P_x_microlimbs[last_limb_index] = split_limb_into_microlimbs(p_x_limbs[last_limb_index], NUM_LAST_LIMB_BITS);
223 P_y_microlimbs[last_limb_index] = split_limb_into_microlimbs(p_y_limbs[last_limb_index], NUM_LAST_LIMB_BITS);
224 current_accumulator_microlimbs[last_limb_index] =
225 split_limb_into_microlimbs(remainder_limbs[last_limb_index], NUM_LAST_LIMB_BITS);
226 quotient_microlimbs[last_limb_index] =
227 split_limb_into_microlimbs(quotient_limbs[last_limb_index], NUM_LAST_QUOTIENT_LIMB_BITS);
228
229 // Split z scalars into microlimbs (handled separately due to different limb count)
230 for (size_t i = 0; i < NUM_Z_LIMBS - 1; i++) {
231 z_1_microlimbs[i] = split_limb_into_microlimbs(z_1_limbs[i], NUM_LIMB_BITS);
232 z_2_microlimbs[i] = split_limb_into_microlimbs(z_2_limbs[i], NUM_LIMB_BITS);
233 }
234 z_1_microlimbs[NUM_Z_LIMBS - 1] =
236 z_2_microlimbs[NUM_Z_LIMBS - 1] =
238
239 // Start filling the witness container
240 AccumulationInput input{
241 .ultra_op = ultra_op,
242 .P_x_limbs = p_x_limbs,
243 .P_x_microlimbs = P_x_microlimbs,
244 .P_y_limbs = p_y_limbs,
245 .P_y_microlimbs = P_y_microlimbs,
246 .z_1_limbs = z_1_limbs,
247 .z_1_microlimbs = z_1_microlimbs,
248 .z_2_limbs = z_2_limbs,
249 .z_2_microlimbs = z_2_microlimbs,
250 .previous_accumulator = previous_accumulator_limbs,
251 .current_accumulator = remainder_limbs,
252 .current_accumulator_microlimbs = current_accumulator_microlimbs,
253 .quotient_binary_limbs = quotient_limbs,
254 .quotient_microlimbs = quotient_microlimbs,
255 .relation_wide_limbs = { low_wide_relation_limb_divided, high_wide_relation_limb_divided },
256 .relation_wide_microlimbs = { split_limb_into_microlimbs(low_wide_relation_limb_divided,
258 split_limb_into_microlimbs(high_wide_relation_limb_divided,
260
261 };
262
263 return input;
264}
265
267{
268 // Opcode should be {0,3,4,8}
269 size_t op_code = ultra_op.op_code.value();
270 BB_ASSERT(op_code == 0 || op_code == 3 || op_code == 4 || op_code == 8);
271
272 // Check and insert x_lo and y_hi into wire 1
275
276 // Check and insert x_hi and z_1 into wire 2
279
280 // Check and insert y_lo and z_2 into wire 3
283}
284
286{
287 // The first wires OpQueue/Transcript wires
289
290 // Check decomposition of values from the Queue into limbs used in bigfield evaluations
291 BB_ASSERT_EQ(acc_step.ultra_op.x_lo, acc_step.P_x_limbs[0] + acc_step.P_x_limbs[1] * SHIFT_1);
292 BB_ASSERT_EQ(acc_step.ultra_op.x_hi, acc_step.P_x_limbs[2] + acc_step.P_x_limbs[3] * SHIFT_1);
293 BB_ASSERT_EQ(acc_step.ultra_op.y_lo, acc_step.P_y_limbs[0] + acc_step.P_y_limbs[1] * SHIFT_1);
294 BB_ASSERT_EQ(acc_step.ultra_op.y_hi, acc_step.P_y_limbs[2] + acc_step.P_y_limbs[3] * SHIFT_1);
295 BB_ASSERT_EQ(acc_step.ultra_op.z_1, acc_step.z_1_limbs[0] + acc_step.z_1_limbs[1] * SHIFT_1);
296 BB_ASSERT_EQ(acc_step.ultra_op.z_2, acc_step.z_2_limbs[0] + acc_step.z_2_limbs[1] * SHIFT_1);
297
298 const auto MAX_Z_LAST_LIMB = uint256_t(1) << (NUM_Z_BITS - NUM_LIMB_BITS);
299 const auto MAX_QUOTIENT_LAST_LIMB = uint256_t(1) << (NUM_LAST_QUOTIENT_LIMB_BITS);
300 // Check limb values are in 68-bit range
303 check_binary_limbs_maximum_values(acc_step.z_1_limbs, /*MAX_LAST_LIMB=*/MAX_Z_LAST_LIMB);
304 check_binary_limbs_maximum_values(acc_step.z_2_limbs, /*MAX_LAST_LIMB=*/MAX_Z_LAST_LIMB);
307 check_binary_limbs_maximum_values(acc_step.quotient_binary_limbs, /*MAX_LAST_LIMB=*/MAX_QUOTIENT_LAST_LIMB);
308
309 // Check limbs used in range constraints are in range
315
316 // Check that relation limbs are in range
319}
320
322{
323 auto& op_wire = std::get<WireIds::OP>(wires);
324 if (ultra_op.op_code.is_random_op) {
325 op_wire.push_back(add_variable(fr(ultra_op.op_code.random_value_1)));
326 op_wire.push_back(add_variable(fr(ultra_op.op_code.random_value_2)));
327 } else {
328 op_wire.push_back(add_variable(fr(ultra_op.op_code.value())));
329 // Similarly to the ColumnPolynomials in the merge protocol, the op_wire is 0 at every second index for a
330 // genuine op
331 op_wire.push_back(zero_idx());
332 }
334
336
338}
339
346{
348
350
351 // Insert limbs used in bigfield evaluations
352 insert_pair_into_wire(P_X_LOW_LIMBS, acc_step.P_x_limbs[0], acc_step.P_x_limbs[1]);
353 insert_pair_into_wire(P_X_HIGH_LIMBS, acc_step.P_x_limbs[2], acc_step.P_x_limbs[3]);
354 insert_pair_into_wire(P_Y_LOW_LIMBS, acc_step.P_y_limbs[0], acc_step.P_y_limbs[1]);
355 insert_pair_into_wire(P_Y_HIGH_LIMBS, acc_step.P_y_limbs[2], acc_step.P_y_limbs[3]);
356 insert_pair_into_wire(Z_LOW_LIMBS, acc_step.z_1_limbs[0], acc_step.z_2_limbs[0]);
357 insert_pair_into_wire(Z_HIGH_LIMBS, acc_step.z_1_limbs[1], acc_step.z_2_limbs[1]);
363
364 // We are using some leftover crevices for relation_wide_microlimbs
365 auto low_relation_microlimbs = acc_step.relation_wide_microlimbs[0];
366 auto high_relation_microlimbs = acc_step.relation_wide_microlimbs[1];
367
368 // We have 4 wires specifically for the relation microlimbs
370 RELATION_WIDE_LIMBS_RANGE_CONSTRAINT_0, low_relation_microlimbs[0], high_relation_microlimbs[0]);
372 RELATION_WIDE_LIMBS_RANGE_CONSTRAINT_1, low_relation_microlimbs[1], high_relation_microlimbs[1]);
374 RELATION_WIDE_LIMBS_RANGE_CONSTRAINT_2, low_relation_microlimbs[2], high_relation_microlimbs[2]);
376 RELATION_WIDE_LIMBS_RANGE_CONSTRAINT_3, low_relation_microlimbs[3], high_relation_microlimbs[3]);
377
378 // Next ones go into top P_x and P_y, current accumulator and quotient unused microlimbs
379
380 // Insert the second highest low relation microlimb into the space left in P_x range constraints highest wire
381 auto top_p_x_microlimbs = acc_step.P_x_microlimbs[NUM_BINARY_LIMBS - 1];
382 top_p_x_microlimbs[NUM_MICRO_LIMBS - 1] = low_relation_microlimbs[NUM_MICRO_LIMBS - 2];
383
384 // Insert the second highest high relation microlimb into the space left in P_y range constraints highest wire
385 auto top_p_y_microlimbs = acc_step.P_y_microlimbs[NUM_BINARY_LIMBS - 1];
386 top_p_y_microlimbs[NUM_MICRO_LIMBS - 1] = high_relation_microlimbs[NUM_MICRO_LIMBS - 2];
387
388 // The highest low relation microlimb goes into the crevice left in current accumulator microlimbs
389 auto top_current_accumulator_microlimbs = acc_step.current_accumulator_microlimbs[NUM_BINARY_LIMBS - 1];
390 top_current_accumulator_microlimbs[NUM_MICRO_LIMBS - 1] = low_relation_microlimbs[NUM_MICRO_LIMBS - 1];
391
392 // The highest high relation microlimb goes into the quotient crevice
393 auto top_quotient_microlimbs = acc_step.quotient_microlimbs[NUM_BINARY_LIMBS - 1];
394 top_quotient_microlimbs[NUM_MICRO_LIMBS - 1] = high_relation_microlimbs[NUM_MICRO_LIMBS - 1];
395
396 // Now put all microlimbs into appropriate wires
414 lay_limbs_in_row(top_current_accumulator_microlimbs, ACCUMULATOR_HIGH_LIMBS_RANGE_CONSTRAINT_0);
419
421
422 // Check that all the wires are filled equally
423 bb::constexpr_for<0, TOTAL_COUNT, 1>([&]<size_t i>() { BB_ASSERT_EQ(std::get<i>(wires).size(), num_gates()); });
424}
425
427{
428 BB_BENCH_NAME("TranslatorCircuitBuilder::feed_ecc_op_queue_into_circuit");
429 using Fq = bb::fq;
430 // If in AVM mode, we use the non-zk reconstructed ultra ops as the structure required by Translator is added by the
431 // GoblinAVM constructor. In Chonk, this structure is given by the zk columns, so we use the zk reconstructed
432 // ultra ops.
433 const auto& ultra_ops =
434 avm_mode ? ecc_op_queue->get_no_zk_reconstructed_ultra_ops() : ecc_op_queue->get_zk_reconstructed_ultra_ops();
435 std::vector<Fq> accumulator_trace;
436 Fq current_accumulator(0);
437 if (ultra_ops.empty()) {
438 return;
439 }
440
441 // Handle the initial UltraOp (a no-op) by filling the start of all wire polynomials with zeros. This ensures
442 // all translator wire polynomials begin with 0, which is necessary for shifted polynomials in the proving system.
443 // Although only the first index needs to be zero, we add two zeros to maintain consistency since each actual
444 // UltraOp populates two polynomial indices.
445 for (auto& wire : wires) {
446 wire.push_back(zero_idx());
447 wire.push_back(zero_idx());
448 }
450
451 for (size_t i = 0; i < NUM_NO_OPS_START; i++) {
452 BB_ASSERT(ultra_ops[i].op_code.value() == 0, "Expected no-op at the start of the op queue");
453 }
454
455 // Process random operations at the start of the op queue (after the initial no-op), no accumulation gates needed.
456 // These ensure commitments and evaluations do not reveal information about op queue content.
457 for (size_t i = NUM_NO_OPS_START; i < NUM_NO_OPS_START + NUM_RANDOM_OPS_START; ++i) {
458 process_random_op(ultra_ops[i]);
459 }
460
461 // Guard against unsigned wraparound when computing ops_end below
462 const size_t min_ops = NUM_NO_OPS_START + NUM_RANDOM_OPS_START + (avm_mode ? 0 : NUM_RANDOM_OPS_END);
463 BB_ASSERT(ultra_ops.size() >= min_ops, "Op queue too small for Translator circuit construction");
464
465 const size_t ops_end = avm_mode ? ultra_ops.size() : ultra_ops.size() - NUM_RANDOM_OPS_END;
466 // Range of UltraOps for which we should construct accumulation gates
467 std::span ultra_ops_span(ultra_ops.begin() + static_cast<std::ptrdiff_t>(NUM_NO_OPS_START + NUM_RANDOM_OPS_START),
468 ultra_ops.begin() + static_cast<std::ptrdiff_t>(ops_end));
469
470 // Pre-compute accumulator values for each step since the circuit processes values in reverse order
471 // and requires knowledge of the previous accumulator to construct each gate. Both accumulator computation
472 // and gate creation skip the random operations at the beginning and end of the op queue and any zero-opcode
473 // padding ops, as these should not influence the final accumulation result (located at index RESULT_ROW).
474 // The accumulation result is sent as part of the Chonk proof, and so we add a genuine operation with
475 // randomly generated values during Chonk execution to ensure no information about the rest of the ops is
476 // leaked. Accumulator pre-computation is achieved by processing the queue in reverse order.
477 for (const auto& ultra_op : std::ranges::reverse_view(ultra_ops_span)) {
478 if (ultra_op.op_code.value() == 0) {
479 // Skip no-ops as they should not affect the computation of the accumulator
480 continue;
481 }
482 current_accumulator *= evaluation_input_x;
483 const auto [x_fq, y_fq] = ultra_op.get_base_point_standard_form();
484 current_accumulator +=
485 Fq(ultra_op.op_code.value()) +
487 (x_fq + batching_challenge_v *
488 (y_fq + batching_challenge_v *
489 (uint256_t(ultra_op.z_1) + batching_challenge_v * uint256_t(ultra_op.z_2))));
490 accumulator_trace.push_back(current_accumulator);
491 }
492
493 // Accumulator final value, recomputed during witness generation and expected at RESULT_ROW
494 Fq final_accumulator_state = accumulator_trace.back();
495 accumulator_trace.pop_back();
496
497 std::array<Fr, NUM_BINARY_LIMBS> previous_accumulator_binary_limbs = split_fq_into_limbs(final_accumulator_state);
498 // Generate witness values and accumulation gates from all the actual UltraOps, starting from beginning
499 for (const auto& ultra_op : ultra_ops_span) {
500 if (ultra_op.op_code.value() == 0) {
501 // For no-op operations, the translator trace remains empty except for accumulator binary limbs,
502 // which are copied from the previous row where a real operation occurred (i.e., where the op wire
503 // at an even index contains a non-zero value). During proving, we verify that the wires at that
504 // previous row are well-formed and that the accumulator value is correctly propagated throughout
505 // the entire no-op range for both even and odd indices.
506 for (size_t j = 0; j < ACCUMULATORS_BINARY_LIMBS_0; j++) {
507 wires[j].push_back(zero_idx());
508 wires[j].push_back(zero_idx());
509 }
510 size_t idx = 0;
511 for (size_t j = ACCUMULATORS_BINARY_LIMBS_0; j <= ACCUMULATORS_BINARY_LIMBS_3; j++) {
512 wires[j].push_back(add_variable(previous_accumulator_binary_limbs[idx]));
513 wires[j].push_back(add_variable(previous_accumulator_binary_limbs[idx]));
514 idx++;
515 }
516 for (size_t j = ACCUMULATORS_BINARY_LIMBS_3 + 1; j < TOTAL_COUNT; j++) {
517 wires[j].push_back(zero_idx());
518 wires[j].push_back(zero_idx());
519 }
521 continue;
522 }
523
524 Fq previous_accumulator{ 0 };
525 // Pop the last value from accumulator trace and use it as previous accumulator
526 if (!accumulator_trace.empty()) {
527 previous_accumulator = accumulator_trace.back();
528 accumulator_trace.pop_back();
529 }
530 // Compute witness values
531 AccumulationInput one_accumulation_step =
532 generate_witness_values(ultra_op, previous_accumulator, batching_challenge_v, evaluation_input_x);
533
534 // Save the state of accumulator in case the next operation encountered is a no-op
535 previous_accumulator_binary_limbs = one_accumulation_step.previous_accumulator;
536 // And put them into the wires
537 create_accumulation_gate(one_accumulation_step);
538 }
539
540 // Also process the last two random ops present at the end of the op queue to hide the ecc ops of the last circuit
541 // whose ops are added to the op queue
542 for (size_t i = ops_end; i < ultra_ops.size(); ++i) {
543 process_random_op(ultra_ops[i]);
544 }
545
546 // The Translator uses a strict 2-row trace structure (even=computation, odd=transfer).
547 // An odd gate count would shift the entire witness layout, corrupting all relations.
548 BB_ASSERT(num_gates() % 2 == 0, "Translator circuit gate count must be even for 2-row trace structure");
549}
551 split_limb_into_microlimbs(const Fr& limb, size_t num_bits)
552{
553 static_assert(MICRO_LIMB_BITS == 14);
554 const size_t num_full_micro_limbs = num_bits / MICRO_LIMB_BITS;
555 const size_t last_limb_bits = num_bits % MICRO_LIMB_BITS;
557
558 // Fill in the full 14-bit microlimbs
559 for (size_t i = 0; i < num_full_micro_limbs; ++i) {
560 microlimbs[i] = uint256_t(limb).slice(i * MICRO_LIMB_BITS, (i + 1) * MICRO_LIMB_BITS);
561 }
562
563 // If there's a partial microlimb at the end, store both actual microlimb and its tail
564 if (last_limb_bits > 0) {
565 microlimbs[num_full_micro_limbs] =
566 uint256_t(limb).slice(num_full_micro_limbs * MICRO_LIMB_BITS, (num_full_micro_limbs + 1) * MICRO_LIMB_BITS);
567 microlimbs[num_full_micro_limbs + 1] = uint256_t(microlimbs[num_full_micro_limbs])
568 << (MICRO_LIMB_BITS - last_limb_bits);
569 }
570 return microlimbs;
571}
572
574{
575 BB_ASSERT(ultra_op.op_code.is_random_op, "function should only be called to process a random op");
577 // Populate the other wires with zeros
578 for (size_t i = WireIds::P_X_LOW_LIMBS; i < wires.size(); i++) {
579 wires[i].push_back(zero_idx());
580 wires[i].push_back(zero_idx());
581 }
583}
584
585} // namespace bb
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:83
#define BB_ASSERT_LTE(left, right,...)
Definition assert.hpp:158
#define BB_ASSERT_LT(left, right,...)
Definition assert.hpp:143
#define BB_BENCH_NAME(name)
Definition bb_bench.hpp:264
virtual uint32_t add_variable(const FF &in)
Add a variable to variables.
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 check_binary_limbs_maximum_values(const std::array< Fr, total_limbs > &limbs, const uint256_t &MAX_LAST_LIMB=(uint256_t(1)<< NUM_LAST_LIMB_BITS))
Assert that all standard limbs are < 2^68 and the last limb is < MAX_LAST_LIMB.
static void assert_well_formed_ultra_op(const UltraOp &ultra_op)
Ensures the ultra op is well-formed and can be used to create a gate.
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 RELATION_WIDE_LIMB_BITS
static constexpr size_t NUM_LAST_QUOTIENT_LIMB_BITS
void lay_limbs_in_row(std::array< Fr, array_size > input, WireIds starting_wire)
Place array_size Fr values into consecutive wire slots starting at starting_wire.
void process_random_op(const UltraOp &ultra_op)
Populate wires for a random op (op wire filled, all other wires zero-filled).
static void check_micro_limbs_maximum_values(const std::array< std::array< Fr, micro_limb_count >, binary_limb_count > &limbs)
Assert that all micro-limbs are < 2^14.
static constexpr uint256_t MAX_RELATION_WIDE_LIMB_SIZE
static std::array< Fr, NUM_MICRO_LIMBS > split_limb_into_microlimbs(const Fr &limb, size_t num_bits)
Split a limb of arbitrary bit size into 14-bit micro-limbs for range constraints.
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.
static std::array< Fr, NUM_BINARY_LIMBS > uint512_t_to_limbs(const uint512_t &original)
Convert a uint512_t value into its 4 68-bit limbs as Fr scalars.
std::array< std::vector< uint32_t >, NUM_WIRES > wires
static std::array< Fr, NUM_Z_LIMBS > split_wide_limb_into_2_limbs(const Fr &wide_limb)
Split a 136-bit Fr value (wide limb) into two 68-bit limbs.
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:309
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
field< Bn254FqParams > fq
Definition fq.hpp:153
field< Bn254FrParams > fr
Definition fr.hpp:155
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_RELATION_WIDE_LIMBS > relation_wide_microlimbs
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 >, 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
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...
static constexpr uint256_t modulus