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
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
50 auto uint512_t_to_limbs = [](const uint512_t& original) {
51 return std::array<Fr, NUM_BINARY_LIMBS>{ Fr(original.slice(0, NUM_LIMB_BITS).lo),
52 Fr(original.slice(NUM_LIMB_BITS, 2 * NUM_LIMB_BITS).lo),
53 Fr(original.slice(2 * NUM_LIMB_BITS, 3 * NUM_LIMB_BITS).lo),
54 Fr(original.slice(3 * NUM_LIMB_BITS, 4 * NUM_LIMB_BITS).lo) };
55 };
56
60 auto split_wide_limb_into_2_limbs = [](const Fr& wide_limb) {
62 Fr(uint256_t(wide_limb).slice(NUM_LIMB_BITS, 2 * NUM_LIMB_BITS)) };
63 };
64
72 auto split_limb_into_microlimbs = [](const Fr& limb, const size_t num_bits) {
73 static_assert(MICRO_LIMB_BITS == 14);
74 size_t num_full_micro_limbs = num_bits / MICRO_LIMB_BITS;
75 size_t last_limb_bits = num_bits % MICRO_LIMB_BITS;
77
78 // Fill in the full 14-bit microlimbs
79 for (size_t i = 0; i < num_full_micro_limbs; ++i) {
80 microlimbs[i] = uint256_t(limb).slice(i * MICRO_LIMB_BITS, (i + 1) * MICRO_LIMB_BITS);
81 }
82
83 // If there's a partial microlimb at the end, store both actual microlimb and its tail
84 if (last_limb_bits > 0) {
85 // Extract up to the next 14-bit boundary (actual)
86 microlimbs[num_full_micro_limbs] = uint256_t(limb).slice(num_full_micro_limbs * MICRO_LIMB_BITS,
87 (num_full_micro_limbs + 1) * MICRO_LIMB_BITS);
88
89 // Store the shifted version in the next slot (tail microlimb)
90 microlimbs[num_full_micro_limbs + 1] = uint256_t(microlimbs[num_full_micro_limbs])
91 << (MICRO_LIMB_BITS - last_limb_bits);
92 }
93 return microlimbs;
94 };
95
96 // x and v are challenges: random values unknown at compile time, treated as witnesses.
98 Fq v_cubed = v_squared * batching_challenge_v;
99 Fq v_quarted = v_cubed * batching_challenge_v;
100
101 // Convert the accumulator, powers of v and x into "bigfield" form
102 auto previous_accumulator_limbs = split_fq_into_limbs(previous_accumulator);
103 auto v_witnesses = split_fq_into_limbs(batching_challenge_v);
104 auto v_squared_witnesses = split_fq_into_limbs(v_squared);
105 auto v_cubed_witnesses = split_fq_into_limbs(v_cubed);
106 auto v_quarted_witnesses = split_fq_into_limbs(v_quarted);
107 auto x_witnesses = split_fq_into_limbs(evaluation_input_x);
108
109 // To calculate the quotient, we need to evaluate the expression in integers. So we need uint512_t versions of all
110 // elements involved
111 size_t op_code = ultra_op.op_code.value();
112 auto uint_previous_accumulator = uint512_t(previous_accumulator);
113 auto uint_x = uint512_t(evaluation_input_x);
114 auto uint_op = uint512_t(op_code);
115 auto uint_p_x = uint512_t(uint256_t(ultra_op.x_lo) + (uint256_t(ultra_op.x_hi) << (NUM_LIMB_BITS << 1)));
116 auto uint_p_y = uint512_t(uint256_t(ultra_op.y_lo) + (uint256_t(ultra_op.y_hi) << (NUM_LIMB_BITS << 1)));
117 auto uint_z1 = uint512_t(ultra_op.z_1);
118 auto uint_z2 = uint512_t(ultra_op.z_2);
119 auto uint_v = uint512_t(batching_challenge_v);
120 auto uint_v_squared = uint512_t(v_squared);
121 auto uint_v_cubed = uint512_t(v_cubed);
122 auto uint_v_quarted = uint512_t(v_quarted);
123
124 // Construct Fq for op, P.x, P.y, z_1, z_2 for use in witness computation
125 Fq base_op = Fq(uint256_t(op_code));
126 Fq base_p_x = Fq(uint256_t(ultra_op.x_lo) + (uint256_t(ultra_op.x_hi) << (NUM_LIMB_BITS << 1)));
127 Fq base_p_y = Fq(uint256_t(ultra_op.y_lo) + (uint256_t(ultra_op.y_hi) << (NUM_LIMB_BITS << 1)));
128 Fq base_z_1 = Fq(uint256_t(ultra_op.z_1));
129 Fq base_z_2 = Fq(uint256_t(ultra_op.z_2));
130
131 // Construct bigfield representations of P.x and P.y
132 auto [p_x_0, p_x_1] = split_wide_limb_into_2_limbs(ultra_op.x_lo);
133 auto [p_x_2, p_x_3] = split_wide_limb_into_2_limbs(ultra_op.x_hi);
134 std::array<Fr, NUM_BINARY_LIMBS> p_x_limbs = { p_x_0, p_x_1, p_x_2, p_x_3 };
135 auto [p_y_0, p_y_1] = split_wide_limb_into_2_limbs(ultra_op.y_lo);
136 auto [p_y_2, p_y_3] = split_wide_limb_into_2_limbs(ultra_op.y_hi);
137 std::array<Fr, NUM_BINARY_LIMBS> p_y_limbs = { p_y_0, p_y_1, p_y_2, p_y_3 };
138
139 // Construct bigfield representations of ultra_op.z_1 and ultra_op.z_2 only using 2 limbs each
140 auto z_1_limbs = split_wide_limb_into_2_limbs(ultra_op.z_1);
141 auto z_2_limbs = split_wide_limb_into_2_limbs(ultra_op.z_2);
142
143 // The formula is `accumulator = accumulator⋅x + (op + v⋅p.x + v²⋅p.y + v³⋅z₁ + v⁴z₂)`. We need to compute the
144 // remainder (new accumulator value)
145 // clang-format off
146 const Fq remainder = previous_accumulator * evaluation_input_x +
147 base_op +
148 base_p_x * batching_challenge_v +
149 base_p_y * v_squared +
150 base_z_1 * v_cubed +
151 base_z_2 * v_quarted;
152
153 // We also need to compute the quotient
154 uint512_t quotient_by_modulus = uint_previous_accumulator * uint_x +
155 uint_op +
156 uint_p_x * uint_v +
157 uint_p_y * uint_v_squared +
158 uint_z1 * uint_v_cubed +
159 uint_z2 * uint_v_quarted -
160 uint512_t(remainder);
161 // clang-format on
162
163 uint512_t quotient = quotient_by_modulus / uint512_t(Fq::modulus);
164
165 BB_ASSERT_EQ(quotient_by_modulus, quotient * uint512_t(Fq::modulus));
166
167 // Compute quotient and remainder bigfield representation
168 auto remainder_limbs = split_fq_into_limbs(remainder);
169 std::array<Fr, NUM_BINARY_LIMBS> quotient_limbs = uint512_t_to_limbs(quotient);
170
171 // We will divide by shift_2 instantly in the relation itself, but first we need to compute the low part (0*0) and
172 // the high part (0*1, 1*0) multiplied by a single limb shift
173 // clang-format off
174 Fr low_wide_relation_limb_part_1 =
175 previous_accumulator_limbs[0] * x_witnesses[0] +
176 op_code +
177 p_x_limbs[0] * v_witnesses[0] +
178 p_y_limbs[0] * v_squared_witnesses[0] +
179 z_1_limbs[0] * v_cubed_witnesses[0] +
180 z_2_limbs[0] * v_quarted_witnesses[0] +
181 quotient_limbs[0] * NEGATIVE_MODULUS_LIMBS[0] -
182 remainder_limbs[0];
183
184 Fr low_wide_relation_limb =
185 low_wide_relation_limb_part_1 +
186 (previous_accumulator_limbs[1] * x_witnesses[0] +
187 previous_accumulator_limbs[0] * x_witnesses[1] +
188 p_x_limbs[0] * v_witnesses[1] +
189 p_x_limbs[1] * v_witnesses[0] +
190 p_y_limbs[0] * v_squared_witnesses[1] +
191 p_y_limbs[1] * v_squared_witnesses[0] +
192 z_1_limbs[0] * v_cubed_witnesses[1] +
193 z_1_limbs[1] * v_cubed_witnesses[0] +
194 z_2_limbs[0] * v_quarted_witnesses[1] +
195 z_2_limbs[1] * v_quarted_witnesses[0] +
196 quotient_limbs[0] * NEGATIVE_MODULUS_LIMBS[1] +
197 quotient_limbs[1] * NEGATIVE_MODULUS_LIMBS[0] -
198 remainder_limbs[1]) * SHIFT_1;
199 // clang-format on
200
201 // Low bits have to be zero
202 BB_ASSERT_EQ(uint256_t(low_wide_relation_limb).slice(0, 2 * NUM_LIMB_BITS), 0U);
203
204 Fr low_wide_relation_limb_divided = low_wide_relation_limb * SHIFT_2_INVERSE;
205
206 // The high relation limb is the accumulation of the low limb divided by 2¹³⁶ and the combination of limbs with
207 // indices (0*2,1*1,2*0) with limbs with indices (0*3,1*2,2*1,3*0) multiplied by 2⁶⁸
208 // clang-format off
209 Fr high_wide_relation_limb_part_1 =
210 low_wide_relation_limb_divided +
211 previous_accumulator_limbs[2] * x_witnesses[0] +
212 previous_accumulator_limbs[1] * x_witnesses[1] +
213 previous_accumulator_limbs[0] * x_witnesses[2] +
214 p_x_limbs[0] * v_witnesses[2] +
215 p_x_limbs[1] * v_witnesses[1] +
216 p_x_limbs[2] * v_witnesses[0] +
217 p_y_limbs[0] * v_squared_witnesses[2] +
218 p_y_limbs[1] * v_squared_witnesses[1] +
219 p_y_limbs[2] * v_squared_witnesses[0] +
220 z_1_limbs[0] * v_cubed_witnesses[2] +
221 z_1_limbs[1] * v_cubed_witnesses[1] +
222 z_2_limbs[0] * v_quarted_witnesses[2] +
223 z_2_limbs[1] * v_quarted_witnesses[1] +
224 quotient_limbs[2] * NEGATIVE_MODULUS_LIMBS[0] +
225 quotient_limbs[1] * NEGATIVE_MODULUS_LIMBS[1] +
226 quotient_limbs[0] * NEGATIVE_MODULUS_LIMBS[2] -
227 remainder_limbs[2];
228
229 Fr high_wide_relation_limb =
230 high_wide_relation_limb_part_1 +
231 (previous_accumulator_limbs[3] * x_witnesses[0] +
232 previous_accumulator_limbs[2] * x_witnesses[1] +
233 previous_accumulator_limbs[1] * x_witnesses[2] +
234 previous_accumulator_limbs[0] * x_witnesses[3] +
235 p_x_limbs[0] * v_witnesses[3] +
236 p_x_limbs[1] * v_witnesses[2] +
237 p_x_limbs[2] * v_witnesses[1] +
238 p_x_limbs[3] * v_witnesses[0] +
239 p_y_limbs[0] * v_squared_witnesses[3] +
240 p_y_limbs[1] * v_squared_witnesses[2] +
241 p_y_limbs[2] * v_squared_witnesses[1] +
242 p_y_limbs[3] * v_squared_witnesses[0] +
243 z_1_limbs[0] * v_cubed_witnesses[3] +
244 z_1_limbs[1] * v_cubed_witnesses[2] +
245 z_2_limbs[0] * v_quarted_witnesses[3] +
246 z_2_limbs[1] * v_quarted_witnesses[2] +
247 quotient_limbs[3] * NEGATIVE_MODULUS_LIMBS[0] +
248 quotient_limbs[2] * NEGATIVE_MODULUS_LIMBS[1] +
249 quotient_limbs[1] * NEGATIVE_MODULUS_LIMBS[2] +
250 quotient_limbs[0] * NEGATIVE_MODULUS_LIMBS[3] -
251 remainder_limbs[3]) * SHIFT_1;
252 // clang-format on
253
254 // Check that the results lower 136 bits are zero
255 BB_ASSERT_EQ(uint256_t(high_wide_relation_limb).slice(0, 2 * NUM_LIMB_BITS), 0U);
256
257 // Get divided version
258 auto high_wide_relation_limb_divided = high_wide_relation_limb * SHIFT_2_INVERSE;
259
260 const auto last_limb_index = NUM_BINARY_LIMBS - 1;
261
266 std::array<std::array<Fr, NUM_MICRO_LIMBS>, NUM_BINARY_LIMBS> current_accumulator_microlimbs;
268
269 // Split all standard limbs (68-bit) into microlimbs for range constraining
270 for (size_t i = 0; i < last_limb_index; i++) {
271 P_x_microlimbs[i] = split_limb_into_microlimbs(p_x_limbs[i], NUM_LIMB_BITS);
272 P_y_microlimbs[i] = split_limb_into_microlimbs(p_y_limbs[i], NUM_LIMB_BITS);
273 current_accumulator_microlimbs[i] = split_limb_into_microlimbs(remainder_limbs[i], NUM_LIMB_BITS);
274 quotient_microlimbs[i] = split_limb_into_microlimbs(quotient_limbs[i], NUM_LIMB_BITS);
275 }
276
277 // Split top limbs (varying bit sizes) into microlimbs
278 P_x_microlimbs[last_limb_index] = split_limb_into_microlimbs(p_x_limbs[last_limb_index], NUM_LAST_LIMB_BITS);
279 P_y_microlimbs[last_limb_index] = split_limb_into_microlimbs(p_y_limbs[last_limb_index], NUM_LAST_LIMB_BITS);
280 current_accumulator_microlimbs[last_limb_index] =
281 split_limb_into_microlimbs(remainder_limbs[last_limb_index], NUM_LAST_LIMB_BITS);
282 quotient_microlimbs[last_limb_index] =
283 split_limb_into_microlimbs(quotient_limbs[last_limb_index], NUM_LAST_QUOTIENT_LIMB_BITS);
284
285 // Split z scalars into microlimbs (handled separately due to different limb count)
286 for (size_t i = 0; i < NUM_Z_LIMBS - 1; i++) {
287 z_1_microlimbs[i] = split_limb_into_microlimbs(z_1_limbs[i], NUM_LIMB_BITS);
288 z_2_microlimbs[i] = split_limb_into_microlimbs(z_2_limbs[i], NUM_LIMB_BITS);
289 }
290 z_1_microlimbs[NUM_Z_LIMBS - 1] =
291 split_limb_into_microlimbs(z_1_limbs[NUM_Z_LIMBS - 1], NUM_Z_BITS - NUM_LIMB_BITS);
292 z_2_microlimbs[NUM_Z_LIMBS - 1] =
293 split_limb_into_microlimbs(z_2_limbs[NUM_Z_LIMBS - 1], NUM_Z_BITS - NUM_LIMB_BITS);
294
295 // Start filling the witness container
296 AccumulationInput input{
297 .ultra_op = ultra_op,
298 .P_x_limbs = p_x_limbs,
299 .P_x_microlimbs = P_x_microlimbs,
300 .P_y_limbs = p_y_limbs,
301 .P_y_microlimbs = P_y_microlimbs,
302 .z_1_limbs = z_1_limbs,
303 .z_1_microlimbs = z_1_microlimbs,
304 .z_2_limbs = z_2_limbs,
305 .z_2_microlimbs = z_2_microlimbs,
306 .previous_accumulator = previous_accumulator_limbs,
307 .current_accumulator = remainder_limbs,
308 .current_accumulator_microlimbs = current_accumulator_microlimbs,
309 .quotient_binary_limbs = quotient_limbs,
310 .quotient_microlimbs = quotient_microlimbs,
311 .relation_wide_limbs = { low_wide_relation_limb_divided, high_wide_relation_limb_divided },
312 .relation_wide_microlimbs = { split_limb_into_microlimbs(low_wide_relation_limb_divided,
314 split_limb_into_microlimbs(high_wide_relation_limb_divided,
316
317 };
318
319 return input;
320}
321
323{
324 // Opcode should be {0,3,4,8}
325 size_t op_code = ultra_op.op_code.value();
326 BB_ASSERT(op_code == 0 || op_code == 3 || op_code == 4 || op_code == 8);
327
328 // Check and insert x_lo and y_hi into wire 1
331
332 // Check and insert x_hi and z_1 into wire 2
335
336 // Check and insert y_lo and z_2 into wire 3
339}
340
342{
343 // The first wires OpQueue/Transcript wires
345
346 // Check decomposition of values from the Queue into limbs used in bigfield evaluations
347 BB_ASSERT_EQ(acc_step.ultra_op.x_lo, acc_step.P_x_limbs[0] + acc_step.P_x_limbs[1] * SHIFT_1);
348 BB_ASSERT_EQ(acc_step.ultra_op.x_hi, acc_step.P_x_limbs[2] + acc_step.P_x_limbs[3] * SHIFT_1);
349 BB_ASSERT_EQ(acc_step.ultra_op.y_lo, acc_step.P_y_limbs[0] + acc_step.P_y_limbs[1] * SHIFT_1);
350 BB_ASSERT_EQ(acc_step.ultra_op.y_hi, acc_step.P_y_limbs[2] + acc_step.P_y_limbs[3] * SHIFT_1);
351 BB_ASSERT_EQ(acc_step.ultra_op.z_1, acc_step.z_1_limbs[0] + acc_step.z_1_limbs[1] * SHIFT_1);
352 BB_ASSERT_EQ(acc_step.ultra_op.z_2, acc_step.z_2_limbs[0] + acc_step.z_2_limbs[1] * SHIFT_1);
353
358 auto check_binary_limbs_maximum_values = []<size_t total_limbs>(const std::array<Fr, total_limbs>& limbs,
359 const uint256_t& MAX_LAST_LIMB =
361 for (size_t i = 0; i < total_limbs - 1; i++) {
362 BB_ASSERT_LT(uint256_t(limbs[i]), SHIFT_1);
363 }
364 BB_ASSERT_LT(uint256_t(limbs[total_limbs - 1]), MAX_LAST_LIMB);
365 };
366
367 const auto MAX_Z_LAST_LIMB = uint256_t(1) << (NUM_Z_BITS - NUM_LIMB_BITS);
368 const auto MAX_QUOTIENT_LAST_LIMB = uint256_t(1) << (NUM_LAST_QUOTIENT_LIMB_BITS);
369 // Check limb values are in 68-bit range
370 check_binary_limbs_maximum_values(acc_step.P_x_limbs);
371 check_binary_limbs_maximum_values(acc_step.P_y_limbs);
372 check_binary_limbs_maximum_values(acc_step.z_1_limbs, /*MAX_LAST_LIMB=*/MAX_Z_LAST_LIMB);
373 check_binary_limbs_maximum_values(acc_step.z_2_limbs, /*MAX_LAST_LIMB=*/MAX_Z_LAST_LIMB);
374 check_binary_limbs_maximum_values(acc_step.previous_accumulator);
375 check_binary_limbs_maximum_values(acc_step.current_accumulator);
376 check_binary_limbs_maximum_values(acc_step.quotient_binary_limbs, /*MAX_LAST_LIMB=*/MAX_QUOTIENT_LAST_LIMB);
377
378 // Check limbs used in range constraints are in range
379 auto check_micro_limbs_maximum_values =
380 []<size_t binary_limb_count, size_t micro_limb_count>(
381 const std::array<std::array<Fr, micro_limb_count>, binary_limb_count>& limbs) {
382 for (size_t i = 0; i < binary_limb_count; i++) {
383 for (size_t j = 0; j < micro_limb_count; j++) {
384 BB_ASSERT_LT(uint256_t(limbs[i][j]), MICRO_SHIFT);
385 }
386 }
387 };
388 check_micro_limbs_maximum_values(acc_step.P_x_microlimbs);
389 check_micro_limbs_maximum_values(acc_step.P_y_microlimbs);
390 check_micro_limbs_maximum_values(acc_step.z_1_microlimbs);
391 check_micro_limbs_maximum_values(acc_step.z_2_microlimbs);
392 check_micro_limbs_maximum_values(acc_step.current_accumulator_microlimbs);
393
394 // Check that relation limbs are in range
397}
398
400{
401 auto& op_wire = std::get<WireIds::OP>(wires);
402 if (ultra_op.op_code.is_random_op) {
403 op_wire.push_back(add_variable(fr(ultra_op.op_code.random_value_1)));
404 op_wire.push_back(add_variable(fr(ultra_op.op_code.random_value_2)));
405 } else {
406 op_wire.push_back(add_variable(fr(ultra_op.op_code.value())));
407 // Similarly to the ColumnPolynomials in the merge protocol, the op_wire is 0 at every second index for a
408 // genuine op
409 op_wire.push_back(zero_idx());
410 }
412
414
416}
417
424{
426
428
429 // Insert limbs used in bigfield evaluations
430 insert_pair_into_wire(P_X_LOW_LIMBS, acc_step.P_x_limbs[0], acc_step.P_x_limbs[1]);
431 insert_pair_into_wire(P_X_HIGH_LIMBS, acc_step.P_x_limbs[2], acc_step.P_x_limbs[3]);
432 insert_pair_into_wire(P_Y_LOW_LIMBS, acc_step.P_y_limbs[0], acc_step.P_y_limbs[1]);
433 insert_pair_into_wire(P_Y_HIGH_LIMBS, acc_step.P_y_limbs[2], acc_step.P_y_limbs[3]);
434 insert_pair_into_wire(Z_LOW_LIMBS, acc_step.z_1_limbs[0], acc_step.z_2_limbs[0]);
435 insert_pair_into_wire(Z_HIGH_LIMBS, acc_step.z_1_limbs[1], acc_step.z_2_limbs[1]);
441
442 // We are using some leftover crevices for relation_wide_microlimbs
443 auto low_relation_microlimbs = acc_step.relation_wide_microlimbs[0];
444 auto high_relation_microlimbs = acc_step.relation_wide_microlimbs[1];
445
446 // We have 4 wires specifically for the relation microlimbs
448 RELATION_WIDE_LIMBS_RANGE_CONSTRAINT_0, low_relation_microlimbs[0], high_relation_microlimbs[0]);
450 RELATION_WIDE_LIMBS_RANGE_CONSTRAINT_1, low_relation_microlimbs[1], high_relation_microlimbs[1]);
452 RELATION_WIDE_LIMBS_RANGE_CONSTRAINT_2, low_relation_microlimbs[2], high_relation_microlimbs[2]);
454 RELATION_WIDE_LIMBS_RANGE_CONSTRAINT_3, low_relation_microlimbs[3], high_relation_microlimbs[3]);
455
456 // Next ones go into top P_x and P_y, current accumulator and quotient unused microlimbs
457
458 // Insert the second highest low relation microlimb into the space left in P_x range constraints highest wire
459 auto top_p_x_microlimbs = acc_step.P_x_microlimbs[NUM_BINARY_LIMBS - 1];
460 top_p_x_microlimbs[NUM_MICRO_LIMBS - 1] = low_relation_microlimbs[NUM_MICRO_LIMBS - 2];
461
462 // Insert the second highest high relation microlimb into the space left in P_y range constraints highest wire
463 auto top_p_y_microlimbs = acc_step.P_y_microlimbs[NUM_BINARY_LIMBS - 1];
464 top_p_y_microlimbs[NUM_MICRO_LIMBS - 1] = high_relation_microlimbs[NUM_MICRO_LIMBS - 2];
465
466 // The highest low relation microlimb goes into the crevice left in current accumulator microlimbs
467 auto top_current_accumulator_microlimbs = acc_step.current_accumulator_microlimbs[NUM_BINARY_LIMBS - 1];
468 top_current_accumulator_microlimbs[NUM_MICRO_LIMBS - 1] = low_relation_microlimbs[NUM_MICRO_LIMBS - 1];
469
470 // The highest high relation microlimb goes into the quotient crevice
471 auto top_quotient_microlimbs = acc_step.quotient_microlimbs[NUM_BINARY_LIMBS - 1];
472 top_quotient_microlimbs[NUM_MICRO_LIMBS - 1] = high_relation_microlimbs[NUM_MICRO_LIMBS - 1];
473
477 auto lay_limbs_in_row = [this]<size_t array_size>(std::array<Fr, array_size> input, WireIds starting_wire) {
478 size_t wire_index = starting_wire;
479 for (auto element : input) {
480 wires[wire_index].push_back(add_variable(element));
481 wire_index++;
482 }
483 };
484
485 // Now put all microlimbs into appropriate wires
486 lay_limbs_in_row(acc_step.P_x_microlimbs[0], P_X_LOW_LIMBS_RANGE_CONSTRAINT_0);
487 lay_limbs_in_row(acc_step.P_x_microlimbs[1], P_X_LOW_LIMBS_RANGE_CONSTRAINT_0);
488 lay_limbs_in_row(acc_step.P_x_microlimbs[2], P_X_HIGH_LIMBS_RANGE_CONSTRAINT_0);
489 lay_limbs_in_row(top_p_x_microlimbs, P_X_HIGH_LIMBS_RANGE_CONSTRAINT_0);
490 lay_limbs_in_row(acc_step.P_y_microlimbs[0], P_Y_LOW_LIMBS_RANGE_CONSTRAINT_0);
491 lay_limbs_in_row(acc_step.P_y_microlimbs[1], P_Y_LOW_LIMBS_RANGE_CONSTRAINT_0);
492 lay_limbs_in_row(acc_step.P_y_microlimbs[2], P_Y_HIGH_LIMBS_RANGE_CONSTRAINT_0);
493 lay_limbs_in_row(top_p_y_microlimbs, P_Y_HIGH_LIMBS_RANGE_CONSTRAINT_0);
494 lay_limbs_in_row(acc_step.z_1_microlimbs[0], Z_LOW_LIMBS_RANGE_CONSTRAINT_0);
495 lay_limbs_in_row(acc_step.z_2_microlimbs[0], Z_LOW_LIMBS_RANGE_CONSTRAINT_0);
496 lay_limbs_in_row(acc_step.z_1_microlimbs[1], Z_HIGH_LIMBS_RANGE_CONSTRAINT_0);
497 lay_limbs_in_row(acc_step.z_2_microlimbs[1], Z_HIGH_LIMBS_RANGE_CONSTRAINT_0);
498 lay_limbs_in_row(acc_step.current_accumulator, ACCUMULATORS_BINARY_LIMBS_0);
499 lay_limbs_in_row(acc_step.previous_accumulator, ACCUMULATORS_BINARY_LIMBS_0);
503 lay_limbs_in_row(top_current_accumulator_microlimbs, ACCUMULATOR_HIGH_LIMBS_RANGE_CONSTRAINT_0);
504 lay_limbs_in_row(acc_step.quotient_microlimbs[0], QUOTIENT_LOW_LIMBS_RANGE_CONSTRAIN_0);
505 lay_limbs_in_row(acc_step.quotient_microlimbs[1], QUOTIENT_LOW_LIMBS_RANGE_CONSTRAIN_0);
506 lay_limbs_in_row(acc_step.quotient_microlimbs[2], QUOTIENT_HIGH_LIMBS_RANGE_CONSTRAIN_0);
507 lay_limbs_in_row(top_quotient_microlimbs, QUOTIENT_HIGH_LIMBS_RANGE_CONSTRAIN_0);
508
510
511 // Check that all the wires are filled equally
512 bb::constexpr_for<0, TOTAL_COUNT, 1>([&]<size_t i>() { BB_ASSERT_EQ(std::get<i>(wires).size(), num_gates()); });
513}
514
516{
517 using Fq = bb::fq;
518 const auto& ultra_ops = ecc_op_queue->get_ultra_ops();
519 std::vector<Fq> accumulator_trace;
520 Fq current_accumulator(0);
521 if (ultra_ops.empty()) {
522 return;
523 }
524
525 // Handle the initial UltraOp (a no-op) by filling the start of all other wire polynomials with zeros. This ensures
526 // all translator wire polynomials begin with 0, which is necessary for shifted polynomials in the proving system.
527 // Although only the first index needs to be zero, we add two zeros to maintain consistency since each actual
528 // UltraOp populates two polynomial indices.
529 for (auto& wire : wires) {
530 wire.push_back(zero_idx());
531 wire.push_back(zero_idx());
532 }
534
535 auto process_random_op = [&](const UltraOp& ultra_op) {
536 BB_ASSERT(ultra_op.op_code.is_random_op, "function should only be called to process a random op");
538 // Populate the other wires with zeros
539 for (size_t i = WireIds::P_X_LOW_LIMBS; i < wires.size(); i++) {
540 wires[i].push_back(zero_idx());
541 wires[i].push_back(zero_idx());
542 }
544 };
545
546 // When encountering the random operations in the op queue, populate the op wire without creating accumulation gates
547 // These are present in the op queue at the beginning and end to ensure commitments and evaluations to op queue
548 // polynomials do not reveal information about data in the op queue
549 // The position and number of these random ops are explained in Chonk::hide_op_queue_content_tail_kernel
550 // and Chonk::hide_op_queue_content_hiding_kernel
551 for (size_t i = NUM_NO_OPS_START; i <= NUM_RANDOM_OPS_START; ++i) {
552 process_random_op(ultra_ops[i]);
553 }
554
555 const size_t ops_end = avm_mode ? ultra_ops.size() : ultra_ops.size() - NUM_RANDOM_OPS_END;
556 // Range of UltraOps for which we should construct accumulation gates
557 std::span ultra_ops_span(ultra_ops.begin() + static_cast<std::ptrdiff_t>(NUM_NO_OPS_START + NUM_RANDOM_OPS_START),
558 ultra_ops.begin() + static_cast<std::ptrdiff_t>(ops_end));
559
560 // Pre-compute accumulator values for each step since the circuit processes values in reverse order
561 // and requires knowledge of the previous accumulator to construct each gate. Both accumulator computation
562 // and gate creation skip the initial no-ops and also the random operations at the beginning and end of the oqueue ,
563 // as these should not influence the final accumulation result (located at index RESULT_ROW). The accumulation
564 // result is sent as part of the Chonk proof, and so we add a genuine operation with randomly generated values
565 // during Chonk execution to ensure no information about the rest of the ops is leaked. Acccumulator pre-computation
566 // is achieved by processing the queue in reverse order.
567 for (const auto& ultra_op : std::ranges::reverse_view(ultra_ops_span)) {
568 if (ultra_op.op_code.value() == 0) {
569 // Skip no-ops as they should not affect the computation of the accumulator
570 continue;
571 }
572 current_accumulator *= evaluation_input_x;
573 const auto [x_fq, y_fq] = ultra_op.get_base_point_standard_form();
574 current_accumulator +=
575 Fq(ultra_op.op_code.value()) +
577 (x_fq + batching_challenge_v *
578 (y_fq + batching_challenge_v *
579 (uint256_t(ultra_op.z_1) + batching_challenge_v * uint256_t(ultra_op.z_2))));
580 accumulator_trace.push_back(current_accumulator);
581 }
582
583 // Accumulator final value, recomputed during witness generation and expected at RESULT_ROW
584 Fq final_accumulator_state = accumulator_trace.back();
585 accumulator_trace.pop_back();
586
587 std::array<Fr, NUM_BINARY_LIMBS> previous_accumulator_binary_limbs = split_fq_into_limbs(final_accumulator_state);
588 // Generate witness values and accumulation gates from all the actual UltraOps, starting from beginning
589 for (const auto& ultra_op : ultra_ops_span) {
590 if (ultra_op.op_code.value() == 0) {
591 // For no-op operations, the translator trace remains empty except for accumulator binary limbs,
592 // which are copied from the previous row where a real operation occurred (i.e., where the op wire
593 // at an even index contains a non-zero value). During proving, we verify that the wires at that
594 // previous row are well-formed and that the accumulator value is correctly propagated throughout
595 // the entire no-op range for both even and odd indices.
596 for (size_t j = 0; j < ACCUMULATORS_BINARY_LIMBS_0; j++) {
597 wires[j].push_back(zero_idx());
598 wires[j].push_back(zero_idx());
599 }
600 size_t idx = 0;
601 for (size_t j = ACCUMULATORS_BINARY_LIMBS_0; j <= ACCUMULATORS_BINARY_LIMBS_3; j++) {
602 wires[j].push_back(add_variable(previous_accumulator_binary_limbs[idx]));
603 wires[j].push_back(add_variable(previous_accumulator_binary_limbs[idx]));
604 idx++;
605 }
606 for (size_t j = ACCUMULATORS_BINARY_LIMBS_3 + 1; j < TOTAL_COUNT; j++) {
607 wires[j].push_back(zero_idx());
608 wires[j].push_back(zero_idx());
609 }
611 continue;
612 }
613
614 Fq previous_accumulator{ 0 };
615 // Pop the last value from accumulator trace and use it as previous accumulator
616 if (!accumulator_trace.empty()) {
617 previous_accumulator = accumulator_trace.back();
618 accumulator_trace.pop_back();
619 }
620 // Compute witness values
621 AccumulationInput one_accumulation_step =
622 generate_witness_values(ultra_op, previous_accumulator, batching_challenge_v, evaluation_input_x);
623
624 // Save the state of accumulator in case the next operation encountered is a no-op
625 previous_accumulator_binary_limbs = one_accumulation_step.previous_accumulator;
626 // And put them into the wires
627 create_accumulation_gate(one_accumulation_step);
628 }
629
630 // Also process the last two random ops present at the end of the op queue to hide the ecc ops of the last circuit
631 // whose ops are added to the op queue
632 for (size_t i = ops_end; i < ultra_ops.size(); ++i) {
633 process_random_op(ultra_ops[i]);
634 }
635}
636} // 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
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 assert_well_formed_ultra_op(const UltraOp &ultra_op)
Ensures the ultra op 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 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
static constexpr uint256_t MAX_RELATION_WIDE_LIMB_SIZE
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.
std::array< std::vector< uint32_t >, NUM_WIRES > wires
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:306
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
static constexpr uint256_t modulus