Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
translator_circuit_checker.cpp
Go to the documentation of this file.
3
4namespace bb {
6 const Fq& batching_challenge_v, const Fq& evaluation_input_x)
7{
8 Fq v_squared = batching_challenge_v * batching_challenge_v;
9 Fq v_cubed = v_squared * batching_challenge_v;
10 Fq v_quarted = v_cubed * batching_challenge_v;
11 return RelationInputs{
12 .x_limbs = Builder::split_fq_into_limbs(evaluation_input_x),
13 .v_limbs = Builder::split_fq_into_limbs(batching_challenge_v),
14 .v_squared_limbs = Builder::split_fq_into_limbs(v_squared),
15 .v_cubed_limbs = Builder::split_fq_into_limbs(v_cubed),
16 .v_quarted_limbs = Builder::split_fq_into_limbs(v_quarted),
17 };
18}
19
21{
22
23 auto wires = circuit.wires;
24
25 auto report_fail = [&](const char* message, size_t row_idx) {
26 info(message, row_idx);
27 return false;
28 };
29
30 // Compute the limbs of evaluation_input_x and powers of batching_challenge_v (these go into the relation)
31 RelationInputs relation_inputs =
33 // Get the main wires (we will operate with range constraint wires mainly through indices, since this is
34 // easier)
35 auto& op_wire = std::get<WireIds::OP>(circuit.wires);
36 auto& x_lo_y_hi_wire = std::get<WireIds::X_LOW_Y_HI>(circuit.wires);
37 auto& x_hi_z_1_wire = std::get<WireIds::X_HIGH_Z_1>(circuit.wires);
38 auto& y_lo_z_2_wire = std::get<WireIds::Y_LOW_Z_2>(circuit.wires);
39 auto& p_x_0_p_x_1_wire = std::get<WireIds::P_X_LOW_LIMBS>(circuit.wires);
40 auto& p_x_2_p_x_3_wire = std::get<WireIds::P_X_HIGH_LIMBS>(circuit.wires);
41 auto& p_y_0_p_y_1_wire = std::get<WireIds::P_Y_LOW_LIMBS>(circuit.wires);
42 auto& p_y_2_p_y_3_wire = std::get<WireIds::P_Y_HIGH_LIMBS>(circuit.wires);
43 auto& z_lo_wire = std::get<WireIds::Z_LOW_LIMBS>(circuit.wires);
44 auto& z_hi_wire = std::get<WireIds::Z_HIGH_LIMBS>(circuit.wires);
45 auto& accumulators_binary_limbs_0_wire = std::get<WireIds::ACCUMULATORS_BINARY_LIMBS_0>(circuit.wires);
46 auto& accumulators_binary_limbs_1_wire = std::get<WireIds::ACCUMULATORS_BINARY_LIMBS_1>(circuit.wires);
47 auto& accumulators_binary_limbs_2_wire = std::get<WireIds::ACCUMULATORS_BINARY_LIMBS_2>(circuit.wires);
48 auto& accumulators_binary_limbs_3_wire = std::get<WireIds::ACCUMULATORS_BINARY_LIMBS_3>(circuit.wires);
49 auto& quotient_low_binary_limbs = std::get<WireIds::QUOTIENT_LOW_BINARY_LIMBS>(circuit.wires);
50 auto& quotient_high_binary_limbs = std::get<WireIds::QUOTIENT_HIGH_BINARY_LIMBS>(circuit.wires);
51 auto& relation_wide_limbs_wire = std::get<WireIds::RELATION_WIDE_LIMBS>(circuit.wires);
52 auto reconstructed_evaluation_input_x = Fr(uint256_t(circuit.evaluation_input_x));
53 auto reconstructed_batching_evaluation_v = Fr(uint256_t(circuit.batching_challenge_v));
54 auto reconstructed_batching_evaluation_v2 = Fr(uint256_t(circuit.batching_challenge_v.pow(2)));
55 auto reconstructed_batching_evaluation_v3 = Fr(uint256_t(circuit.batching_challenge_v.pow(3)));
56 auto reconstructed_batching_evaluation_v4 = Fr(uint256_t(circuit.batching_challenge_v.pow(4)));
61 auto get_sequential_micro_chunks = [&](size_t gate_index, WireIds starting_wire_index, size_t chunk_count) {
62 std::vector<Fr> chunks;
63 for (size_t i = starting_wire_index; i < starting_wire_index + chunk_count; i++) {
64 chunks.push_back(circuit.get_variable(circuit.wires[i][gate_index]));
65 }
66 return chunks;
67 };
68
77 auto accumulate_limb_from_micro_chunks = [](const std::vector<Fr>& chunks, const int skipped_at_end = 1) {
78 Fr mini_accumulator(0);
79 auto end = chunks.end();
80 std::advance(end, -skipped_at_end);
81 for (auto it = end; it != chunks.begin();) {
82 --it;
83 mini_accumulator = mini_accumulator * TranslatorCircuitBuilder::MICRO_SHIFT + *it;
84 }
85 return mini_accumulator;
86 };
87
88 auto check_binary_limbs_equality = [&](const std::vector<Fr>& first, const std::vector<Fr>& second, size_t gate) {
89 for (const auto [first_limb, second_limb] : zip_view(first, second)) {
90 if (first_limb != second_limb) {
91 return report_fail("Binary limbs are not equal = ", gate);
92 }
93 }
94 return true;
95 };
96
97 auto check_accumulator_transfer = [&](const std::vector<Fr>& previous_accumulator, size_t gate) {
98 if (gate % 2 != 1) {
99 return report_fail("accumulator transfer should only be checked at odd gates = ", gate);
100 }
101 if (gate + 1 < circuit.num_gates - 1) {
102 // Check that the next gate's current accumulator equals this gate's previous accumulator
103 const std::vector next_gate_current_accumulator = {
104 circuit.get_variable(accumulators_binary_limbs_0_wire[gate + 1]),
105 circuit.get_variable(accumulators_binary_limbs_1_wire[gate + 1]),
106 circuit.get_variable(accumulators_binary_limbs_2_wire[gate + 1]),
107 circuit.get_variable(accumulators_binary_limbs_3_wire[gate + 1]),
108 };
109 if (!check_binary_limbs_equality(next_gate_current_accumulator, previous_accumulator, gate + 1)) {
110 return false;
111 }
112 } else {
113 // Check accumulator starts at zero
114 for (const auto& limb : previous_accumulator) {
115 if (limb != Fr(0)) {
116 return report_fail("accumulator doesn't start with 0 = ", gate + 1);
117 }
118 }
119 }
120 return true;
121 };
122
123 auto check_no_op =
124 [&](const std::vector<Fr>& current_accumulator, const std::vector<Fr>& previous_accumulator, size_t gate) {
125 if (!check_binary_limbs_equality(current_accumulator, previous_accumulator, gate)) {
126 return false;
127 }
128 return check_accumulator_transfer(previous_accumulator, gate + 1);
129 };
130
131 auto check_random_op_code = [&](const Fr op_code, size_t gate) {
132 if (gate % 2 == 0) {
133 if (op_code == Fr(0) || op_code == Fr(3) || op_code == Fr(4) || op_code == Fr(8)) {
134 return report_fail("Opcode should be random value at even gate = ", gate);
135 }
136 } else {
137 if (op_code == Fr(0)) {
138 return report_fail("Opcode should be 0 at odd gate = ", gate);
139 }
140 }
141 return true;
142 };
143
144 // TODO(https: // github.com/AztecProtocol/barretenberg/issues/1367): Report all failures more explicitly and
145 // consider making use of relations.
146 auto in_random_range = [&](size_t i) {
147 return (i >= 2 * Builder::NUM_NO_OPS_START && i < RESULT_ROW) ||
148 (i >= circuit.num_gates - (circuit.avm_mode ? 0 : 2 * Builder::NUM_RANDOM_OPS_END) &&
149 i < circuit.num_gates);
150 };
151 for (size_t i = 2; i < circuit.num_gates - 1; i += 2) {
152
153 // Ensure random op is present in expected ranges
154 Fr op_code = circuit.get_variable(op_wire[i]);
155 if (in_random_range(i)) {
156 check_random_op_code(op_code, i);
157 Fr op_code_next = circuit.get_variable(op_wire[i + 1]);
158 check_random_op_code(op_code_next, i + 1);
159 continue;
160 }
161
162 // Current accumulator (updated value)
163 const std::vector current_accumulator_binary_limbs = {
164 circuit.get_variable(accumulators_binary_limbs_0_wire[i]),
165 circuit.get_variable(accumulators_binary_limbs_1_wire[i]),
166 circuit.get_variable(accumulators_binary_limbs_2_wire[i]),
167 circuit.get_variable(accumulators_binary_limbs_3_wire[i]),
168 };
169
170 // Previous accumulator
171 const std::vector previous_accumulator_binary_limbs = {
172 circuit.get_variable(accumulators_binary_limbs_0_wire[i + 1]),
173 circuit.get_variable(accumulators_binary_limbs_1_wire[i + 1]),
174 circuit.get_variable(accumulators_binary_limbs_2_wire[i + 1]),
175 circuit.get_variable(accumulators_binary_limbs_3_wire[i + 1]),
176 };
177
178 if (op_code == 0) {
179 if (!check_no_op(current_accumulator_binary_limbs, previous_accumulator_binary_limbs, i)) {
180 return false;
181 };
182 continue;
183 }
184
185 Fr p_x_lo = circuit.get_variable(x_lo_y_hi_wire[i]);
186 Fr p_x_hi = circuit.get_variable(x_hi_z_1_wire[i]);
187 Fr p_x_0 = circuit.get_variable(p_x_0_p_x_1_wire[i]);
188 Fr p_x_1 = circuit.get_variable(p_x_0_p_x_1_wire[i + 1]);
189 Fr p_x_2 = circuit.get_variable(p_x_2_p_x_3_wire[i]);
190 Fr p_x_3 = circuit.get_variable(p_x_2_p_x_3_wire[i + 1]);
191 const std::vector p_x_binary_limbs = { p_x_0, p_x_1, p_x_2, p_x_3 };
192
193 // P.y
194 Fr p_y_lo = circuit.get_variable(y_lo_z_2_wire[i]);
195 Fr p_y_hi = circuit.get_variable(x_lo_y_hi_wire[i + 1]);
196 Fr p_y_0 = circuit.get_variable(p_y_0_p_y_1_wire[i]);
197 Fr p_y_1 = circuit.get_variable(p_y_0_p_y_1_wire[i + 1]);
198 Fr p_y_2 = circuit.get_variable(p_y_2_p_y_3_wire[i]);
199 Fr p_y_3 = circuit.get_variable(p_y_2_p_y_3_wire[i + 1]);
200 const std::vector p_y_binary_limbs = { p_y_0, p_y_1, p_y_2, p_y_3 };
201 // z1, z2
202 Fr z_1 = circuit.get_variable(x_hi_z_1_wire[i + 1]);
203 Fr z_2 = circuit.get_variable(y_lo_z_2_wire[i + 1]);
204
205 Fr z_1_lo = circuit.get_variable(z_lo_wire[i]);
206 Fr z_2_lo = circuit.get_variable(z_lo_wire[i + 1]);
207 Fr z_1_hi = circuit.get_variable(z_hi_wire[i]);
208 Fr z_2_hi = circuit.get_variable(z_hi_wire[i + 1]);
209
210 const std::vector z_1_binary_limbs = { z_1_lo, z_1_hi };
211 const std::vector z_2_binary_limbs = { z_2_lo, z_2_hi };
212 // Relation limbs
213 Fr low_wide_relation_limb = circuit.get_variable(relation_wide_limbs_wire[i]);
214 Fr high_wide_relation_limb = circuit.get_variable(relation_wide_limbs_wire[i + 1]);
215
216 // Quotient
217 const std::vector quotient_binary_limbs = {
218 circuit.get_variable(quotient_low_binary_limbs[i]),
219 circuit.get_variable(quotient_low_binary_limbs[i + 1]),
220 circuit.get_variable(quotient_high_binary_limbs[i]),
221 circuit.get_variable(quotient_high_binary_limbs[i + 1]),
222 };
223
224 const size_t NUM_MICRO_LIMBS = Builder::NUM_MICRO_LIMBS;
225
226 // Get micro chunks for checking decomposition and range
227 auto p_x_micro_chunks = {
228 get_sequential_micro_chunks(i, WireIds::P_X_LOW_LIMBS_RANGE_CONSTRAINT_0, NUM_MICRO_LIMBS),
229 get_sequential_micro_chunks(i + 1, WireIds::P_X_LOW_LIMBS_RANGE_CONSTRAINT_0, NUM_MICRO_LIMBS),
230 get_sequential_micro_chunks(i, WireIds::P_X_HIGH_LIMBS_RANGE_CONSTRAINT_0, NUM_MICRO_LIMBS),
231 get_sequential_micro_chunks(i + 1, WireIds::P_X_HIGH_LIMBS_RANGE_CONSTRAINT_0, NUM_MICRO_LIMBS)
232 };
233 auto p_y_micro_chunks = {
234 get_sequential_micro_chunks(i, WireIds::P_Y_LOW_LIMBS_RANGE_CONSTRAINT_0, NUM_MICRO_LIMBS),
235 get_sequential_micro_chunks(i + 1, WireIds::P_Y_LOW_LIMBS_RANGE_CONSTRAINT_0, NUM_MICRO_LIMBS),
236 get_sequential_micro_chunks(i, WireIds::P_Y_HIGH_LIMBS_RANGE_CONSTRAINT_0, NUM_MICRO_LIMBS),
237 get_sequential_micro_chunks(i + 1, WireIds::P_Y_HIGH_LIMBS_RANGE_CONSTRAINT_0, NUM_MICRO_LIMBS)
238 };
239 auto z_1_micro_chunks = {
240 get_sequential_micro_chunks(i, WireIds::Z_LOW_LIMBS_RANGE_CONSTRAINT_0, NUM_MICRO_LIMBS),
241 get_sequential_micro_chunks(i, WireIds::Z_HIGH_LIMBS_RANGE_CONSTRAINT_0, NUM_MICRO_LIMBS),
242 };
243
244 auto z_2_micro_chunks = {
245
246 get_sequential_micro_chunks(i + 1, WireIds::Z_LOW_LIMBS_RANGE_CONSTRAINT_0, NUM_MICRO_LIMBS),
247 get_sequential_micro_chunks(i + 1, WireIds::Z_HIGH_LIMBS_RANGE_CONSTRAINT_0, NUM_MICRO_LIMBS)
248 };
249
250 auto current_accumulator_micro_chunks = {
251 get_sequential_micro_chunks(i, WireIds::ACCUMULATOR_LOW_LIMBS_RANGE_CONSTRAINT_0, NUM_MICRO_LIMBS),
252 get_sequential_micro_chunks(i + 1, WireIds::ACCUMULATOR_LOW_LIMBS_RANGE_CONSTRAINT_0, NUM_MICRO_LIMBS),
253 get_sequential_micro_chunks(i, WireIds::ACCUMULATOR_HIGH_LIMBS_RANGE_CONSTRAINT_0, NUM_MICRO_LIMBS),
254 get_sequential_micro_chunks(i + 1, WireIds::ACCUMULATOR_HIGH_LIMBS_RANGE_CONSTRAINT_0, NUM_MICRO_LIMBS),
255 };
256 auto quotient_micro_chunks = {
257 get_sequential_micro_chunks(i, WireIds::QUOTIENT_LOW_LIMBS_RANGE_CONSTRAIN_0, NUM_MICRO_LIMBS),
258 get_sequential_micro_chunks(i + 1, WireIds::QUOTIENT_LOW_LIMBS_RANGE_CONSTRAIN_0, NUM_MICRO_LIMBS),
259 get_sequential_micro_chunks(i, WireIds::QUOTIENT_HIGH_LIMBS_RANGE_CONSTRAIN_0, NUM_MICRO_LIMBS),
260 get_sequential_micro_chunks(i + 1, WireIds::QUOTIENT_HIGH_LIMBS_RANGE_CONSTRAIN_0, NUM_MICRO_LIMBS),
261 };
262
263 // Lambda for checking the correctness of decomposition of values in the Queue into limbs for
264 // checking the relation
265 auto check_wide_limb_into_binary_limb_relation = [](const std::vector<Fr>& wide_limbs,
266 const std::vector<Fr>& binary_limbs) {
267 BB_ASSERT_EQ(wide_limbs.size() * 2, binary_limbs.size());
268 for (size_t i = 0; i < wide_limbs.size(); i++) {
269 if ((binary_limbs[i * 2] + Fr(Builder::SHIFT_1) * binary_limbs[i * 2 + 1]) != wide_limbs[i]) {
270 return false;
271 }
272 }
273 return true;
274 };
275 // Check that everything has been decomposed correctly
276 // P.xₗₒ = P.xₗₒ_0 + SHIFT_1 * P.xₗₒ_1
277 // P.xₕᵢ = P.xₕᵢ_0 + SHIFT_1 * P.xₕᵢ_1
278 // z_1 = z_1ₗₒ + SHIFT_1 * z_1ₕᵢ
279 // z_2 = z_2ₗₒ + SHIFT_2 * z_1ₕᵢ
280 if (!(check_wide_limb_into_binary_limb_relation({ p_x_lo, p_x_hi }, p_x_binary_limbs) &&
281 check_wide_limb_into_binary_limb_relation({ p_y_lo, p_y_hi }, p_y_binary_limbs) &&
282 check_wide_limb_into_binary_limb_relation({ z_1 }, z_1_binary_limbs) &&
283 check_wide_limb_into_binary_limb_relation({ z_2 }, z_2_binary_limbs))) {
284
285 return report_fail("wide limb decomposition failied at row = ", i);
286 }
287
288 enum LimbSeriesType { STANDARD_COORDINATE, Z_SCALAR, QUOTIENT };
289
290 // Check that limbs have been decomposed into microlimbs correctly
291 // value = ∑ (2ˡ)ⁱ⋅ chunkᵢ, where 2ˡ is the shift
292 auto check_micro_limb_decomposition_correctness = [&accumulate_limb_from_micro_chunks](
293 const std::vector<Fr>& binary_limbs,
294 const std::vector<std::vector<Fr>>& micro_limbs,
295 const LimbSeriesType limb_series_type) {
296 // Shifts for decompositions
297 constexpr auto SHIFT_12_TO_14 = Fr(4);
298 constexpr auto SHIFT_10_TO_14 = Fr(16);
299 constexpr auto SHIFT_8_TO_14 = Fr(64);
300 constexpr auto SHIFT_4_TO_14 = Fr(1024);
301
302 BB_ASSERT_EQ(binary_limbs.size(), micro_limbs.size());
303 // First check that all the microlimbs are properly range constrained
304 for (auto& micro_limb_series : micro_limbs) {
305 for (auto& micro_limb : micro_limb_series) {
306 if (uint256_t(micro_limb) > Builder::MAX_MICRO_LIMB_SIZE) {
307 return false;
308 }
309 }
310 }
311 // For low limbs the last microlimb is used with the shift, so we skip it when reconstructing
312 // the limb
313 const size_t SKIPPED_FOR_LOW_LIMBS = 1;
314 for (size_t i = 0; i < binary_limbs.size() - 1; i++) {
315 if (binary_limbs[i] != accumulate_limb_from_micro_chunks(micro_limbs[i], SKIPPED_FOR_LOW_LIMBS)) {
316 return false;
317 }
318 // Check last additional constraint (68->70)
319 if (micro_limbs[i][NUM_MICRO_LIMBS - 1] != (SHIFT_12_TO_14 * micro_limbs[i][NUM_MICRO_LIMBS - 2])) {
320 return false;
321 }
322 }
323
324 const size_t SKIPPED_FOR_STANDARD = 2;
325 const size_t SKIPPED_FOR_Z_SCALARS = 1;
326 const size_t SKIPPED_FOR_QUOTIENT = 2;
327 switch (limb_series_type) {
328 case STANDARD_COORDINATE:
329 // For standard Fq value the highest limb is 50 bits, so we skip the top 2 microlimbs
330 if (binary_limbs[binary_limbs.size() - 1] !=
331 accumulate_limb_from_micro_chunks(micro_limbs[binary_limbs.size() - 1], SKIPPED_FOR_STANDARD)) {
332 return false;
333 }
334 // Check last additional constraint (50->56)
335 if (micro_limbs[binary_limbs.size() - 1][NUM_MICRO_LIMBS - SKIPPED_FOR_STANDARD] !=
336 (SHIFT_8_TO_14 *
337 micro_limbs[binary_limbs.size() - 1][NUM_MICRO_LIMBS - SKIPPED_FOR_STANDARD - 1])) {
338
339 return false;
340 }
341 break;
342 // For z top limbs we need as many microlimbs as for the low limbs
343 case Z_SCALAR:
344 if (binary_limbs[binary_limbs.size() - 1] !=
345 accumulate_limb_from_micro_chunks(micro_limbs[binary_limbs.size() - 1], SKIPPED_FOR_Z_SCALARS)) {
346 return false;
347 }
348 // Check last additional constraint (60->70)
349 if (micro_limbs[binary_limbs.size() - 1][NUM_MICRO_LIMBS - SKIPPED_FOR_Z_SCALARS] !=
350 (SHIFT_4_TO_14 *
351 micro_limbs[binary_limbs.size() - 1][NUM_MICRO_LIMBS - SKIPPED_FOR_Z_SCALARS - 1])) {
352 return false;
353 }
354 break;
355 // Quotient also doesn't need the top 2
356 case QUOTIENT:
357 if (binary_limbs[binary_limbs.size() - 1] !=
358 accumulate_limb_from_micro_chunks(micro_limbs[binary_limbs.size() - 1], SKIPPED_FOR_QUOTIENT)) {
359 return false;
360 }
361 // Check last additional constraint (52->56)
362 if (micro_limbs[binary_limbs.size() - 1][NUM_MICRO_LIMBS - SKIPPED_FOR_QUOTIENT] !=
363 (SHIFT_10_TO_14 *
364 micro_limbs[binary_limbs.size() - 1][NUM_MICRO_LIMBS - SKIPPED_FOR_QUOTIENT - 1])) {
365 return false;
366 }
367 break;
368 default:
369 abort();
370 }
371
372 return true;
373 };
374 // Check all micro limb decompositions
375 if (!check_micro_limb_decomposition_correctness(p_x_binary_limbs, p_x_micro_chunks, STANDARD_COORDINATE)) {
376 return false;
377 }
378 if (!check_micro_limb_decomposition_correctness(p_y_binary_limbs, p_y_micro_chunks, STANDARD_COORDINATE)) {
379 return false;
380 }
381 if (!check_micro_limb_decomposition_correctness(z_1_binary_limbs, z_1_micro_chunks, Z_SCALAR)) {
382 return false;
383 }
384 if (!check_micro_limb_decomposition_correctness(z_2_binary_limbs, z_2_micro_chunks, Z_SCALAR)) {
385 return false;
386 }
387 if (!check_micro_limb_decomposition_correctness(
388 current_accumulator_binary_limbs, current_accumulator_micro_chunks, STANDARD_COORDINATE)) {
389 return false;
390 }
391 if (!check_micro_limb_decomposition_correctness(quotient_binary_limbs, quotient_micro_chunks, QUOTIENT)) {
392 return false;
393 }
394
395 // The logic we are trying to enforce is:
396 // current_accumulator = previous_accumulator ⋅ x + op_code + P.x ⋅ v + P.y ⋅ v² + z_1 ⋅ v³ + z_2 ⋅
397 // v⁴ mod Fq To ensure this we transform the relation into the form: previous_accumulator ⋅ x + op +
398 // P.x ⋅ v + P.y ⋅ v² + z_1 ⋅ v³ + z_2 ⋅ v⁴ - quotient ⋅ p - current_accumulator = 0 However, we
399 // don't have integers. Despite that, we can approximate integers for a certain range, if we know
400 // that there will not be any overflows. For now we set the range to 2²⁷² ⋅ r. We can evaluate the
401 // logic modulo 2²⁷² with range constraints and r is native.
402 //
403 // previous_accumulator ⋅ x + op + P.x ⋅ v + P.y ⋅ v² + z_1 ⋅ v³ + z_2 ⋅ v⁴ - quotient ⋅ p -
404 // current_accumulator = 0 =>
405 // 1. previous_accumulator ⋅ x + op + P.x ⋅ v + P.y ⋅ v² + z_1 ⋅ v³ + z_2 ⋅ v⁴ + quotient ⋅ (-p mod
406 // 2²⁷²) - current_accumulator = 0 mod 2²⁷²
407 // 2. previous_accumulator ⋅ x + op + P.x ⋅ v + P.y ⋅ v² + z_1 ⋅ v³ + z_2 ⋅ v⁴ - quotient ⋅ p -
408 // current_accumulator = 0 mod r
409 //
410 // The second relation is straightforward and easy to check. The first, not so much. We have to
411 // evaluate certain bit chunks of the equation and ensure that they are zero. For example, for the
412 // lowest limb it would be (inclusive ranges):
413 //
414 // previous_accumulator[0:67] ⋅ x[0:67] + op + P.x[0:67] ⋅ v[0:67] + P.y[0:67] ⋅ v²[0:67] +
415 // z_1[0:67] ⋅ v³[0:67] + z_2[0:67] ⋅ v⁴[0:67] + quotient[0:67] ⋅ (-p mod 2²⁷²)[0:67] -
416 // current_accumulator[0:67] = intermediate_value; (we don't take parts of op, because it's supposed
417 // to be between 0 and 3)
418 //
419 // We could check that this intermediate_value is equal to 0 mod 2⁶⁸ by dividing it by 2⁶⁸ and
420 // constraining it. For efficiency, we actually compute wider evaluations for 136 bits, which
421 // require us to also obtain and shift products of [68:135] by [0:67] and [0:67] by [68:135] bits.
422 // The result of division goes into the next evaluation (the same as a carry flag would)
423 // So the lowest wide limb is : (∑everything[0:67]⋅everything[0:67] +
424 // 2⁶⁸⋅(∑everything[0:67]⋅everything[68:135]))/ 2¹³⁶
425 //
426 // The high is:
427 // (low_limb + ∑everything[0:67]⋅everything[136:203] + ∑everything[68:135]⋅everything[68:135] +
428 // 2⁶⁸(∑everything[0:67]⋅everything[204:271] + ∑everything[68:135]⋅everything[136:203])) / 2¹³⁶
429 //
430 // We also limit computation on limbs of op, z_1 and z_2, since we know that op has only the lowest
431 // limb and z_1 and z_2 have only the two lowest limbs
432 constexpr std::array<Fr, 5> NEGATIVE_MODULUS_LIMBS = Builder::NEGATIVE_MODULUS_LIMBS;
433 const uint256_t SHIFT_1 = Builder::SHIFT_1;
434 const uint256_t SHIFT_2 = Builder::SHIFT_2;
435 const uint256_t SHIFT_3 = Builder::SHIFT_3;
436 Fr low_wide_limb_relation_check =
437
438 (previous_accumulator_binary_limbs[0] * relation_inputs.x_limbs[0] + op_code +
439 relation_inputs.v_limbs[0] * p_x_0 + relation_inputs.v_squared_limbs[0] * p_y_0 +
440 relation_inputs.v_cubed_limbs[0] * z_1_lo + relation_inputs.v_quarted_limbs[0] * z_2_lo +
441 quotient_binary_limbs[0] * NEGATIVE_MODULUS_LIMBS[0] - current_accumulator_binary_limbs[0]) +
442 (previous_accumulator_binary_limbs[1] * relation_inputs.x_limbs[0] + relation_inputs.v_limbs[1] * p_x_0 +
443 relation_inputs.v_squared_limbs[1] * p_y_0 + relation_inputs.v_cubed_limbs[1] * z_1_lo +
444 relation_inputs.v_quarted_limbs[1] * z_2_lo + quotient_binary_limbs[1] * NEGATIVE_MODULUS_LIMBS[0] +
445 previous_accumulator_binary_limbs[0] * relation_inputs.x_limbs[1] + relation_inputs.v_limbs[0] * p_x_1 +
446 relation_inputs.v_squared_limbs[0] * p_y_1 + relation_inputs.v_cubed_limbs[0] * z_1_hi +
447 relation_inputs.v_quarted_limbs[0] * z_2_hi + quotient_binary_limbs[0] * NEGATIVE_MODULUS_LIMBS[1] -
448 current_accumulator_binary_limbs[1]) *
449 Fr(SHIFT_1);
450 if (low_wide_limb_relation_check != (low_wide_relation_limb * SHIFT_2)) {
451 return false;
452 }
453 Fr high_wide_relation_limb_check =
454 low_wide_relation_limb + previous_accumulator_binary_limbs[2] * relation_inputs.x_limbs[0] +
455 previous_accumulator_binary_limbs[1] * relation_inputs.x_limbs[1] +
456 previous_accumulator_binary_limbs[0] * relation_inputs.x_limbs[2] + relation_inputs.v_limbs[2] * p_x_0 +
457 relation_inputs.v_limbs[1] * p_x_1 + relation_inputs.v_limbs[0] * p_x_2 +
458 relation_inputs.v_squared_limbs[2] * p_y_0 + relation_inputs.v_squared_limbs[1] * p_y_1 +
459 relation_inputs.v_squared_limbs[0] * p_y_2 + relation_inputs.v_cubed_limbs[2] * z_1_lo +
460 relation_inputs.v_cubed_limbs[1] * z_1_hi + relation_inputs.v_quarted_limbs[2] * z_2_lo +
461 relation_inputs.v_quarted_limbs[1] * z_2_hi + quotient_binary_limbs[2] * NEGATIVE_MODULUS_LIMBS[0] +
462 quotient_binary_limbs[1] * NEGATIVE_MODULUS_LIMBS[1] +
463 quotient_binary_limbs[0] * NEGATIVE_MODULUS_LIMBS[2] - current_accumulator_binary_limbs[2] +
464 (previous_accumulator_binary_limbs[3] * relation_inputs.x_limbs[0] +
465 previous_accumulator_binary_limbs[2] * relation_inputs.x_limbs[1] +
466 previous_accumulator_binary_limbs[1] * relation_inputs.x_limbs[2] +
467 previous_accumulator_binary_limbs[0] * relation_inputs.x_limbs[3] + relation_inputs.v_limbs[3] * p_x_0 +
468 relation_inputs.v_limbs[2] * p_x_1 + relation_inputs.v_limbs[1] * p_x_2 +
469 relation_inputs.v_limbs[0] * p_x_3 + relation_inputs.v_squared_limbs[3] * p_y_0 +
470 relation_inputs.v_squared_limbs[2] * p_y_1 + relation_inputs.v_squared_limbs[1] * p_y_2 +
471 relation_inputs.v_squared_limbs[0] * p_y_3 + relation_inputs.v_cubed_limbs[3] * z_1_lo +
472 relation_inputs.v_cubed_limbs[2] * z_1_hi + relation_inputs.v_quarted_limbs[3] * z_2_lo +
473 relation_inputs.v_quarted_limbs[2] * z_2_hi + quotient_binary_limbs[3] * NEGATIVE_MODULUS_LIMBS[0] +
474 quotient_binary_limbs[2] * NEGATIVE_MODULUS_LIMBS[1] +
475 quotient_binary_limbs[1] * NEGATIVE_MODULUS_LIMBS[2] +
476 quotient_binary_limbs[0] * NEGATIVE_MODULUS_LIMBS[3] - current_accumulator_binary_limbs[3]) *
477 SHIFT_1;
478 if (high_wide_relation_limb_check != (high_wide_relation_limb * SHIFT_2)) {
479 return false;
480 }
481 // Apart from checking the correctness of the evaluation modulo 2²⁷² we also need to ensure that the
482 // logic works in our scalar field. For this we reconstruct the scalar field values from individual
483 // limbs
484 auto reconstructed_p_x = (p_x_0 + p_x_1 * SHIFT_1 + p_x_2 * SHIFT_2 + p_x_3 * SHIFT_3);
485 auto reconstructed_p_y = (p_y_0 + p_y_1 * SHIFT_1 + p_y_2 * SHIFT_2 + p_y_3 * SHIFT_3);
486 auto reconstructed_current_accumulator =
487 (current_accumulator_binary_limbs[0] + current_accumulator_binary_limbs[1] * SHIFT_1 +
488 current_accumulator_binary_limbs[2] * SHIFT_2 + current_accumulator_binary_limbs[3] * SHIFT_3);
489 auto reconstructed_previous_accumulator =
490 (previous_accumulator_binary_limbs[0] + previous_accumulator_binary_limbs[1] * SHIFT_1 +
491 previous_accumulator_binary_limbs[2] * SHIFT_2 + previous_accumulator_binary_limbs[3] * SHIFT_3);
492
493 auto reconstructed_z1 = (z_1_lo + z_1_hi * SHIFT_1);
494 auto reconstructed_z2 = (z_2_lo + z_2_hi * SHIFT_1);
495 auto reconstructed_quotient = (quotient_binary_limbs[0] + quotient_binary_limbs[1] * SHIFT_1 +
496 quotient_binary_limbs[2] * SHIFT_2 + quotient_binary_limbs[3] * SHIFT_3);
497
498 // Check the relation
499 if (!(reconstructed_previous_accumulator * reconstructed_evaluation_input_x + op_code +
500 reconstructed_p_x * reconstructed_batching_evaluation_v +
501 reconstructed_p_y * reconstructed_batching_evaluation_v2 +
502 reconstructed_z1 * reconstructed_batching_evaluation_v3 +
503 reconstructed_z2 * reconstructed_batching_evaluation_v4 +
504 reconstructed_quotient * NEGATIVE_MODULUS_LIMBS[4] - reconstructed_current_accumulator)
505 .is_zero()) {
506 return false;
507 };
508
509 if (!check_accumulator_transfer(previous_accumulator_binary_limbs, i + 1)) {
510 return false;
511 }
512 };
513 return true;
514}
515}; // namespace bb
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:88
FF get_variable(const uint32_t index) const
Get the value of the variable v_{index}.
TranslatorCircuitBuilder creates a circuit that evaluates the correctness of the evaluation of EccOpQ...
static constexpr std::array< Fr, 5 > NEGATIVE_MODULUS_LIMBS
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.
std::array< SlabVector< uint32_t >, NUM_WIRES > wires
WireIds
There are so many wires that naming them has no sense, it is easier to access them with enums.
static RelationInputs compute_relation_inputs_limbs(const Fq &batching_challenge_v, const Fq &evaluation_input_x)
Create limb representations of x and powers of v that are needed to compute the witness or check circ...
static bool check(const Builder &circuit)
Check the witness satisifies the circuit.
void info(Args... args)
Definition log.hpp:74
Entry point for Barretenberg command-line interface.
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
BB_INLINE constexpr field pow(const uint256_t &exponent) const noexcept