Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
ultra_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
16#include "rom_ram_logic.hpp"
17
20#include <execution>
21#include <unordered_map>
22#include <unordered_set>
23
24namespace bb {
25
26template <typename ExecutionTrace>
28{
54 if (!circuit_finalized) {
55 if (ensure_nonzero) {
56 add_gates_to_ensure_all_polys_are_non_zero();
57 }
58 process_non_native_field_multiplications();
59#ifndef ULTRA_FUZZ
60 this->rom_ram_logic.process_ROM_arrays(this);
61 this->rom_ram_logic.process_RAM_arrays(this);
62 process_range_lists();
63#endif
64 populate_public_inputs_block();
65 circuit_finalized = true;
66 } else {
67 // Gates added after first call to finalize will not be processed since finalization is only performed once
68 info("WARNING: Redundant call to finalize_circuit(). Is this intentional?");
69 }
70}
71
77// TODO(#423): This function adds valid (but arbitrary) gates to ensure that the circuit which includes
78// them will not result in any zero-polynomials. It also ensures that the first coefficient of the wire
79// polynomials is zero, which is required for them to be shiftable.
80template <typename ExecutionTrace>
82{
83 // q_m, q_1, q_2, q_3, q_4
84 blocks.arithmetic.populate_wires(this->zero_idx(), this->zero_idx(), this->zero_idx(), this->zero_idx());
85 blocks.arithmetic.q_m().emplace_back(1);
86 blocks.arithmetic.q_1().emplace_back(1);
87 blocks.arithmetic.q_2().emplace_back(1);
88 blocks.arithmetic.q_3().emplace_back(1);
89 blocks.arithmetic.q_4().emplace_back(1);
90 blocks.arithmetic.q_c().emplace_back(0);
91 blocks.arithmetic.set_gate_selector(0);
92 check_selector_length_consistency();
93 ++this->num_gates;
94
95 // q_delta_range
96 blocks.delta_range.populate_wires(this->zero_idx(), this->zero_idx(), this->zero_idx(), this->zero_idx());
97 blocks.delta_range.q_m().emplace_back(0);
98 blocks.delta_range.q_1().emplace_back(0);
99 blocks.delta_range.q_2().emplace_back(0);
100 blocks.delta_range.q_3().emplace_back(0);
101 blocks.delta_range.q_4().emplace_back(0);
102 blocks.delta_range.q_c().emplace_back(0);
103 blocks.delta_range.set_gate_selector(1);
104
105 check_selector_length_consistency();
106 ++this->num_gates;
107 create_unconstrained_gate(
108 blocks.delta_range, this->zero_idx(), this->zero_idx(), this->zero_idx(), this->zero_idx());
109
110 // q_elliptic
111 blocks.elliptic.populate_wires(this->zero_idx(), this->zero_idx(), this->zero_idx(), this->zero_idx());
112 blocks.elliptic.q_m().emplace_back(0);
113 blocks.elliptic.q_1().emplace_back(0);
114 blocks.elliptic.q_2().emplace_back(0);
115 blocks.elliptic.q_3().emplace_back(0);
116 blocks.elliptic.q_4().emplace_back(0);
117 blocks.elliptic.q_c().emplace_back(0);
118 blocks.elliptic.set_gate_selector(1);
119 check_selector_length_consistency();
120 ++this->num_gates;
121 create_unconstrained_gate(blocks.elliptic, this->zero_idx(), this->zero_idx(), this->zero_idx(), this->zero_idx());
122
123 // q_memory
124 blocks.memory.populate_wires(this->zero_idx(), this->zero_idx(), this->zero_idx(), this->zero_idx());
125 blocks.memory.q_m().emplace_back(0);
126 blocks.memory.q_1().emplace_back(0);
127 blocks.memory.q_2().emplace_back(0);
128 blocks.memory.q_3().emplace_back(0);
129 blocks.memory.q_4().emplace_back(0);
130 blocks.memory.q_c().emplace_back(0);
131 blocks.memory.set_gate_selector(1);
132 check_selector_length_consistency();
133 ++this->num_gates;
134 create_unconstrained_gate(blocks.memory, this->zero_idx(), this->zero_idx(), this->zero_idx(), this->zero_idx());
135
136 // q_nnf
137 blocks.nnf.populate_wires(this->zero_idx(), this->zero_idx(), this->zero_idx(), this->zero_idx());
138 blocks.nnf.q_m().emplace_back(0);
139 blocks.nnf.q_1().emplace_back(0);
140 blocks.nnf.q_2().emplace_back(0);
141 blocks.nnf.q_3().emplace_back(0);
142 blocks.nnf.q_4().emplace_back(0);
143 blocks.nnf.q_c().emplace_back(0);
144 blocks.nnf.set_gate_selector(1);
145 check_selector_length_consistency();
146 ++this->num_gates;
147 create_unconstrained_gate(blocks.nnf, this->zero_idx(), this->zero_idx(), this->zero_idx(), this->zero_idx());
148
149 // Add nonzero values in w_4 and q_c (q_4*w_4 + q_c --> 1*1 - 1 = 0)
150 uint32_t one_idx = put_constant_variable(FF::one());
151 create_big_add_gate({ this->zero_idx(), this->zero_idx(), this->zero_idx(), one_idx, 0, 0, 0, 1, -1 });
152
153 // Take care of all polys related to lookups (q_lookup, tables, sorted, etc)
154 // by doing a dummy lookup with a special table.
155 // Note: the 4th table poly is the table index: this is not the value of the table
156 // type enum but rather the index of the table in the list of all tables utilized
157 // in the circuit. Therefore we naively need two different basic tables (indices 0, 1)
158 // to get a non-zero value in table_4.
159 // The multitable operates on 2-bit values, so the maximum is 3
160 uint32_t left_value = 3;
161 uint32_t right_value = 3;
162
163 FF left_witness_value = fr{ left_value, 0, 0, 0 }.to_montgomery_form();
164 FF right_witness_value = fr{ right_value, 0, 0, 0 }.to_montgomery_form();
165
166 uint32_t left_witness_index = this->add_variable(left_witness_value);
167 uint32_t right_witness_index = this->add_variable(right_witness_value);
168 const auto dummy_accumulators = plookup::get_lookup_accumulators(
169 plookup::MultiTableId::HONK_DUMMY_MULTI, left_witness_value, right_witness_value, true);
170 auto read_data = create_gates_from_plookup_accumulators(
171 plookup::MultiTableId::HONK_DUMMY_MULTI, dummy_accumulators, left_witness_index, right_witness_index);
172
173 update_used_witnesses(left_witness_index);
174 update_used_witnesses(right_witness_index);
175 std::array<std::vector<uint32_t>, 3> parse_read_data{ read_data[plookup::ColumnIdx::C1],
176 read_data[plookup::ColumnIdx::C2],
177 read_data[plookup::ColumnIdx::C3] };
178 for (const auto& column : parse_read_data) {
179 update_used_witnesses(column);
180 update_finalize_witnesses(column);
181 }
182
183 // mock a poseidon external gate, with all zeros as input
184 blocks.poseidon2_external.populate_wires(this->zero_idx(), this->zero_idx(), this->zero_idx(), this->zero_idx());
185 blocks.poseidon2_external.q_m().emplace_back(0);
186 blocks.poseidon2_external.q_1().emplace_back(0);
187 blocks.poseidon2_external.q_2().emplace_back(0);
188 blocks.poseidon2_external.q_3().emplace_back(0);
189 blocks.poseidon2_external.q_c().emplace_back(0);
190 blocks.poseidon2_external.q_4().emplace_back(0);
191 blocks.poseidon2_external.set_gate_selector(1);
192 check_selector_length_consistency();
193 ++this->num_gates;
194
195 // unconstrained gate to be read into by previous poseidon external gate via shifts
196 create_unconstrained_gate(
197 blocks.poseidon2_external, this->zero_idx(), this->zero_idx(), this->zero_idx(), this->zero_idx());
198
199 // mock a poseidon internal gate, with all zeros as input
200 blocks.poseidon2_internal.populate_wires(this->zero_idx(), this->zero_idx(), this->zero_idx(), this->zero_idx());
201 blocks.poseidon2_internal.q_m().emplace_back(0);
202 blocks.poseidon2_internal.q_1().emplace_back(0);
203 blocks.poseidon2_internal.q_2().emplace_back(0);
204 blocks.poseidon2_internal.q_3().emplace_back(0);
205 blocks.poseidon2_internal.q_c().emplace_back(0);
206 blocks.poseidon2_internal.q_4().emplace_back(0);
207 blocks.poseidon2_internal.set_gate_selector(1);
208 check_selector_length_consistency();
209 ++this->num_gates;
210
211 // dummy gate to be read into by previous poseidon internal gate via shifts
212 create_unconstrained_gate(
213 blocks.poseidon2_internal, this->zero_idx(), this->zero_idx(), this->zero_idx(), this->zero_idx());
214}
224template <typename ExecutionTrace> void UltraCircuitBuilder_<ExecutionTrace>::create_add_gate(const add_triple_<FF>& in)
225{
226 this->assert_valid_variables({ in.a, in.b, in.c });
227
228 blocks.arithmetic.populate_wires(in.a, in.b, in.c, this->zero_idx());
229 blocks.arithmetic.q_m().emplace_back(0);
230 blocks.arithmetic.q_1().emplace_back(in.a_scaling);
231 blocks.arithmetic.q_2().emplace_back(in.b_scaling);
232 blocks.arithmetic.q_3().emplace_back(in.c_scaling);
233 blocks.arithmetic.q_c().emplace_back(in.const_scaling);
234 blocks.arithmetic.q_4().emplace_back(0);
235 blocks.arithmetic.set_gate_selector(1);
236 check_selector_length_consistency();
237 ++this->num_gates;
238}
239
248template <typename ExecutionTrace>
250 const bool include_next_gate_w_4)
251{
252 this->assert_valid_variables({ in.a, in.b, in.c, in.d });
253 blocks.arithmetic.populate_wires(in.a, in.b, in.c, in.d);
254 blocks.arithmetic.q_m().emplace_back(include_next_gate_w_4 ? in.mul_scaling * FF(2) : in.mul_scaling);
255 blocks.arithmetic.q_1().emplace_back(in.a_scaling);
256 blocks.arithmetic.q_2().emplace_back(in.b_scaling);
257 blocks.arithmetic.q_3().emplace_back(in.c_scaling);
258 blocks.arithmetic.q_c().emplace_back(in.const_scaling);
259 blocks.arithmetic.q_4().emplace_back(in.d_scaling);
260 blocks.arithmetic.set_gate_selector(include_next_gate_w_4 ? 2 : 1);
261 check_selector_length_consistency();
262 ++this->num_gates;
263}
264
273template <typename ExecutionTrace>
275 const bool include_next_gate_w_4)
276{
277 this->assert_valid_variables({ in.a, in.b, in.c, in.d });
278 blocks.arithmetic.populate_wires(in.a, in.b, in.c, in.d);
279 blocks.arithmetic.q_m().emplace_back(0);
280 blocks.arithmetic.q_1().emplace_back(in.a_scaling);
281 blocks.arithmetic.q_2().emplace_back(in.b_scaling);
282 blocks.arithmetic.q_3().emplace_back(in.c_scaling);
283 blocks.arithmetic.q_c().emplace_back(in.const_scaling);
284 blocks.arithmetic.q_4().emplace_back(in.d_scaling);
285 blocks.arithmetic.set_gate_selector(include_next_gate_w_4 ? 2 : 1);
286 check_selector_length_consistency();
287 ++this->num_gates;
289
295template <typename ExecutionTrace>
298 this->assert_valid_variables({ in.a, in.b, in.c, in.d });
300 blocks.arithmetic.populate_wires(in.a, in.b, in.c, in.d);
301 blocks.arithmetic.q_m().emplace_back(in.mul_scaling);
302 blocks.arithmetic.q_1().emplace_back(in.a_scaling);
303 blocks.arithmetic.q_2().emplace_back(in.b_scaling);
304 blocks.arithmetic.q_3().emplace_back(in.c_scaling);
305 blocks.arithmetic.q_c().emplace_back(in.const_scaling);
306 blocks.arithmetic.q_4().emplace_back(in.d_scaling);
307 blocks.arithmetic.set_gate_selector(1);
308 check_selector_length_consistency();
309 ++this->num_gates;
310}
311
319template <typename ExecutionTrace> void UltraCircuitBuilder_<ExecutionTrace>::create_mul_gate(const mul_triple_<FF>& in)
320{
321 this->assert_valid_variables({ in.a, in.b, in.c });
322
323 blocks.arithmetic.populate_wires(in.a, in.b, in.c, this->zero_idx());
324 blocks.arithmetic.q_m().emplace_back(in.mul_scaling);
325 blocks.arithmetic.q_1().emplace_back(0);
326 blocks.arithmetic.q_2().emplace_back(0);
327 blocks.arithmetic.q_3().emplace_back(in.c_scaling);
328 blocks.arithmetic.q_c().emplace_back(in.const_scaling);
329 blocks.arithmetic.q_4().emplace_back(0);
330 blocks.arithmetic.set_gate_selector(1);
331 check_selector_length_consistency();
332 ++this->num_gates;
333}
339template <typename ExecutionTrace>
341{
342 this->assert_valid_variables({ variable_index });
344 blocks.arithmetic.populate_wires(variable_index, variable_index, this->zero_idx(), this->zero_idx());
345 blocks.arithmetic.q_m().emplace_back(1);
346 blocks.arithmetic.q_1().emplace_back(-1);
347 blocks.arithmetic.q_2().emplace_back(0);
348 blocks.arithmetic.q_3().emplace_back(0);
349 blocks.arithmetic.q_c().emplace_back(0);
350 blocks.arithmetic.q_4().emplace_back(0);
351 blocks.arithmetic.set_gate_selector(1);
352 check_selector_length_consistency();
353 ++this->num_gates;
354}
355
362template <typename ExecutionTrace>
364{
365 this->assert_valid_variables({ in.a, in.b, in.c });
366
367 blocks.arithmetic.populate_wires(in.a, in.b, in.c, this->zero_idx());
368 blocks.arithmetic.q_m().emplace_back(in.q_m);
369 blocks.arithmetic.q_1().emplace_back(in.q_l);
370 blocks.arithmetic.q_2().emplace_back(in.q_r);
371 blocks.arithmetic.q_3().emplace_back(in.q_o);
372 blocks.arithmetic.q_c().emplace_back(in.q_c);
373 blocks.arithmetic.q_4().emplace_back(0);
374 blocks.arithmetic.set_gate_selector(1);
375 check_selector_length_consistency();
376 ++this->num_gates;
377}
378
387template <typename ExecutionTrace>
389{
398 this->assert_valid_variables({ in.x1, in.x2, in.x3, in.y1, in.y2, in.y3 });
399
400 auto& block = blocks.elliptic;
401
402 bool previous_elliptic_gate_exists = block.size() > 0;
403 bool can_fuse_into_previous_gate = previous_elliptic_gate_exists;
404 // TODO(https://github.com/AztecProtocol/barretenberg/issues/1482): scrutinize and clean up this logic
405 if (can_fuse_into_previous_gate) {
406 can_fuse_into_previous_gate = can_fuse_into_previous_gate && (block.w_r()[block.size() - 1] == in.x1);
407 can_fuse_into_previous_gate = can_fuse_into_previous_gate && (block.w_o()[block.size() - 1] == in.y1);
408 can_fuse_into_previous_gate = can_fuse_into_previous_gate && (block.q_3()[block.size() - 1] == 0);
409 can_fuse_into_previous_gate = can_fuse_into_previous_gate && (block.q_4()[block.size() - 1] == 0);
410 can_fuse_into_previous_gate = can_fuse_into_previous_gate && (block.q_1()[block.size() - 1] == 0);
411 can_fuse_into_previous_gate = can_fuse_into_previous_gate && (block.q_arith()[block.size() - 1] == 0);
412 can_fuse_into_previous_gate = can_fuse_into_previous_gate && (block.q_m()[block.size() - 1] == 0);
413 }
414
415 // AUDITTODO: use this instead.
416 [[maybe_unused]] bool can_fuse =
417 block.size() > 0 && /* a previous gate exists in the block */
418 block.w_r()[block.size() - 1] == in.x1 && /* and output x coord of previous gate is input of this one */
419 block.w_o()[block.size() - 1] == in.y1; /* and output y coord of previous gate is input of this one */
420
421 if (can_fuse_into_previous_gate) {
422 block.q_1().set(block.size() - 1, in.sign_coefficient);
423 block.q_elliptic().set(block.size() - 1, 1);
424 } else {
425 block.populate_wires(this->zero_idx(), in.x1, in.y1, this->zero_idx());
426 block.q_3().emplace_back(0);
427 block.q_4().emplace_back(0);
428 block.q_1().emplace_back(in.sign_coefficient);
429
430 block.q_2().emplace_back(0);
431 block.q_m().emplace_back(0);
432 block.q_c().emplace_back(0);
433 block.set_gate_selector(1);
434 check_selector_length_consistency();
435 ++this->num_gates;
436 }
437 create_unconstrained_gate(block, in.x2, in.x3, in.y3, in.y2);
438}
439
445template <typename ExecutionTrace>
447{
448 auto& block = blocks.elliptic;
449
458 this->assert_valid_variables({ in.x1, in.x3, in.y1, in.y3 });
459
460 bool previous_elliptic_gate_exists = block.size() > 0;
461 bool can_fuse_into_previous_gate = previous_elliptic_gate_exists;
462 // TODO(https://github.com/AztecProtocol/barretenberg/issues/1482): scrutinize and clean up this logic
463 if (can_fuse_into_previous_gate) {
464 can_fuse_into_previous_gate = can_fuse_into_previous_gate && (block.w_r()[block.size() - 1] == in.x1);
465 can_fuse_into_previous_gate = can_fuse_into_previous_gate && (block.w_o()[block.size() - 1] == in.y1);
466 can_fuse_into_previous_gate = can_fuse_into_previous_gate && (block.q_arith()[block.size() - 1] == 0);
467 can_fuse_into_previous_gate = can_fuse_into_previous_gate && (block.q_lookup_type()[block.size() - 1] == 0);
468 can_fuse_into_previous_gate = can_fuse_into_previous_gate && (block.q_memory()[block.size() - 1] == 0);
469 can_fuse_into_previous_gate = can_fuse_into_previous_gate && (block.q_nnf()[block.size() - 1] == 0);
470 }
471
472 // AUDITTODO: use this instead.
473 [[maybe_unused]] bool can_fuse =
474 block.size() > 0 && /* a previous gate exists in the block */
475 block.w_r()[block.size() - 1] == in.x1 && /* and output x coord of previous gate is input of this one */
476 block.w_o()[block.size() - 1] == in.y1; /* and output y coord of previous gate is input of this one */
477
478 if (can_fuse_into_previous_gate) {
479 block.q_elliptic().set(block.size() - 1, 1);
480 block.q_m().set(block.size() - 1, 1);
481 } else {
482 block.populate_wires(this->zero_idx(), in.x1, in.y1, this->zero_idx());
483 block.q_m().emplace_back(1);
484 block.q_1().emplace_back(0);
485 block.q_2().emplace_back(0);
486 block.q_3().emplace_back(0);
487 block.q_c().emplace_back(0);
488 block.q_4().emplace_back(0);
489 block.set_gate_selector(1);
490 check_selector_length_consistency();
491 ++this->num_gates;
492 }
493 create_unconstrained_gate(block, this->zero_idx(), in.x3, in.y3, this->zero_idx());
494}
495
502template <typename ExecutionTrace>
503void UltraCircuitBuilder_<ExecutionTrace>::fix_witness(const uint32_t witness_index, const FF& witness_value)
504{
505 this->assert_valid_variables({ witness_index });
506
507 blocks.arithmetic.populate_wires(witness_index, this->zero_idx(), this->zero_idx(), this->zero_idx());
508 blocks.arithmetic.q_m().emplace_back(0);
509 blocks.arithmetic.q_1().emplace_back(1);
510 blocks.arithmetic.q_2().emplace_back(0);
511 blocks.arithmetic.q_3().emplace_back(0);
512 blocks.arithmetic.q_c().emplace_back(-witness_value);
513 blocks.arithmetic.q_4().emplace_back(0);
514 blocks.arithmetic.set_gate_selector(1);
515 check_selector_length_consistency();
516 ++this->num_gates;
517}
518
519template <typename ExecutionTrace>
521{
522 if (constant_variable_indices.contains(variable)) {
523 return constant_variable_indices.at(variable);
524 } else {
525 uint32_t variable_index = this->add_variable(variable);
526 fix_witness(variable_index, variable);
527 constant_variable_indices.insert({ variable, variable_index });
528 return variable_index;
529 }
530}
531
540template <typename ExecutionTrace>
542{
543 for (plookup::BasicTable& table : lookup_tables) {
544 if (table.id == id) {
545 return table;
546 }
547 }
548 // Table doesn't exist! So try to create it.
549 lookup_tables.emplace_back(plookup::create_basic_table(id, lookup_tables.size()));
550 return lookup_tables.back();
551}
552
557template <typename ExecutionTrace>
559 const plookup::MultiTableId& id,
560 const plookup::ReadData<FF>& read_values,
561 const uint32_t key_a_index,
562 std::optional<uint32_t> key_b_index)
563{
564 const auto& multi_table = plookup::get_multitable(id);
565 const size_t num_lookups = read_values[plookup::ColumnIdx::C1].size();
567 for (size_t i = 0; i < num_lookups; ++i) {
568 // get basic lookup table; construct and add to builder.lookup_tables if not already present
569 auto& table = get_table(multi_table.basic_table_ids[i]);
570
571 table.lookup_gates.emplace_back(read_values.lookup_entries[i]); // used for constructing sorted polynomials
572
573 const auto first_idx = (i == 0) ? key_a_index : this->add_variable(read_values[plookup::ColumnIdx::C1][i]);
574 const auto second_idx = (i == 0 && (key_b_index.has_value()))
575 ? key_b_index.value()
576 : this->add_variable(read_values[plookup::ColumnIdx::C2][i]);
577 const auto third_idx = this->add_variable(read_values[plookup::ColumnIdx::C3][i]);
578
579 read_data[plookup::ColumnIdx::C1].push_back(first_idx);
580 read_data[plookup::ColumnIdx::C2].push_back(second_idx);
581 read_data[plookup::ColumnIdx::C3].push_back(third_idx);
582 this->assert_valid_variables({ first_idx, second_idx, third_idx });
583
584 blocks.lookup.q_3().emplace_back(FF(table.table_index));
585 blocks.lookup.populate_wires(first_idx, second_idx, third_idx, this->zero_idx());
586 blocks.lookup.q_1().emplace_back(0);
587 blocks.lookup.q_2().emplace_back((i == (num_lookups - 1) ? 0 : -multi_table.column_1_step_sizes[i + 1]));
588 blocks.lookup.q_m().emplace_back((i == (num_lookups - 1) ? 0 : -multi_table.column_2_step_sizes[i + 1]));
589 blocks.lookup.q_c().emplace_back((i == (num_lookups - 1) ? 0 : -multi_table.column_3_step_sizes[i + 1]));
590 blocks.lookup.q_4().emplace_back(0);
591 blocks.lookup.set_gate_selector(1);
592 check_selector_length_consistency();
593 ++this->num_gates;
594 }
595 return read_data;
596}
597
601template <typename ExecutionTrace>
603 const uint64_t target_range)
604{
605 RangeList result;
606 const auto range_tag = get_new_tag(); // current_tag + 1;
607 const auto tau_tag = get_new_tag(); // current_tag + 2;
608 create_tag(range_tag, tau_tag);
609 create_tag(tau_tag, range_tag);
610 result.target_range = target_range;
611 result.range_tag = range_tag;
612 result.tau_tag = tau_tag;
613
614 uint64_t num_multiples_of_three = (target_range / DEFAULT_PLOOKUP_RANGE_STEP_SIZE);
615
616 result.variable_indices.reserve((uint32_t)num_multiples_of_three);
617 for (uint64_t i = 0; i <= num_multiples_of_three; ++i) {
618 const uint32_t index = this->add_variable(i * DEFAULT_PLOOKUP_RANGE_STEP_SIZE);
619 result.variable_indices.emplace_back(index);
620 assign_tag(index, result.range_tag);
621 }
622 {
623 const uint32_t index = this->add_variable(target_range);
624 result.variable_indices.emplace_back(index);
625 assign_tag(index, result.range_tag);
627 // Need this because these variables will not appear in the witness otherwise
628 create_unconstrained_gates(result.variable_indices);
630 return result;
631}
632
633// range constraint a value by decomposing it into limbs whose size should be the default range constraint size
634
635template <typename ExecutionTrace>
637 const uint32_t variable_index, const uint64_t num_bits, const uint64_t target_range_bitnum, std::string const& msg)
639 this->assert_valid_variables({ variable_index });
640
641 BB_ASSERT_GT(num_bits, 0U);
642
643 uint256_t val = (uint256_t)(this->get_variable(variable_index));
644
645 // If the value is out of range, set the CircuitBuilder error to the given msg.
646 if (val.get_msb() >= num_bits && !this->failed()) {
647 this->failure(msg);
648 }
649
650 const uint64_t sublimb_mask = (1ULL << target_range_bitnum) - 1;
651
665 std::vector<uint64_t> sublimbs;
666 std::vector<uint32_t> sublimb_indices;
668 const bool has_remainder_bits = (num_bits % target_range_bitnum != 0);
669 const uint64_t num_limbs = (num_bits / target_range_bitnum) + has_remainder_bits;
670 const uint64_t last_limb_size = num_bits - ((num_bits / target_range_bitnum) * target_range_bitnum);
671 const uint64_t last_limb_range = ((uint64_t)1 << last_limb_size) - 1;
672
673 uint256_t accumulator = val;
674 for (size_t i = 0; i < num_limbs; ++i) {
675 sublimbs.push_back(accumulator.data[0] & sublimb_mask);
676 accumulator = accumulator >> target_range_bitnum;
677 }
678 for (size_t i = 0; i < sublimbs.size(); ++i) {
679 const auto limb_idx = this->add_variable(sublimbs[i]);
680 sublimb_indices.emplace_back(limb_idx);
681 if ((i == sublimbs.size() - 1) && has_remainder_bits) {
682 create_new_range_constraint(limb_idx, last_limb_range);
683 } else {
684 create_new_range_constraint(limb_idx, sublimb_mask);
685 }
686 }
687
688 const uint64_t num_limb_triples = (num_limbs / 3) + ((num_limbs % 3) != 0);
689 const uint64_t leftovers = (num_limbs % 3) == 0 ? 3 : (num_limbs % 3);
690
691 accumulator = val;
692 uint32_t accumulator_idx = variable_index;
693
694 for (size_t i = 0; i < num_limb_triples; ++i) {
695 const bool real_limbs[3]{
696 (i == (num_limb_triples - 1) && (leftovers < 1)) ? false : true,
697 (i == (num_limb_triples - 1) && (leftovers < 2)) ? false : true,
698 (i == (num_limb_triples - 1) && (leftovers < 3)) ? false : true,
699 };
700
701 const uint64_t round_sublimbs[3]{
702 real_limbs[0] ? sublimbs[3 * i] : 0,
703 real_limbs[1] ? sublimbs[3 * i + 1] : 0,
704 real_limbs[2] ? sublimbs[3 * i + 2] : 0,
705 };
706 const uint32_t new_limbs[3]{
707 real_limbs[0] ? sublimb_indices[3 * i] : this->zero_idx(),
708 real_limbs[1] ? sublimb_indices[3 * i + 1] : this->zero_idx(),
709 real_limbs[2] ? sublimb_indices[3 * i + 2] : this->zero_idx(),
710 };
711 const uint64_t shifts[3]{
712 target_range_bitnum * (3 * i),
713 target_range_bitnum * (3 * i + 1),
714 target_range_bitnum * (3 * i + 2),
715 };
716 uint256_t new_accumulator = accumulator - (uint256_t(round_sublimbs[0]) << shifts[0]) -
717 (uint256_t(round_sublimbs[1]) << shifts[1]) -
718 (uint256_t(round_sublimbs[2]) << shifts[2]);
719
720 create_big_add_gate(
722 new_limbs[0],
723 new_limbs[1],
724 new_limbs[2],
725 accumulator_idx,
726 uint256_t(1) << shifts[0],
727 uint256_t(1) << shifts[1],
728 uint256_t(1) << shifts[2],
729 -1,
730 0,
731 },
732 ((i == num_limb_triples - 1) ? false : true));
733 // TODO(https://github.com/AztecProtocol/barretenberg/issues/1450): this is probably creating an unused
734 // wire/variable in the circuit, in the last iteration of the loop.
735 accumulator_idx = this->add_variable(new_accumulator);
736 accumulator = new_accumulator;
738 return sublimb_indices;
739}
740
750template <typename ExecutionTrace>
752 const uint64_t target_range,
753 std::string const msg)
754{
755 const bool is_out_of_range = (uint256_t(this->get_variable(variable_index)).data[0] > target_range);
756 if (is_out_of_range && !this->failed()) {
757 this->failure(msg);
758 }
759 if (range_lists.count(target_range) == 0) {
760 range_lists.insert({ target_range, create_range_list(target_range) });
761 }
762
763 const auto existing_tag = this->real_variable_tags[this->real_variable_index[variable_index]];
764 auto& list = range_lists[target_range];
765
766 // If the variable's tag matches the target range list's tag, do nothing.
767 if (existing_tag != list.range_tag) {
768 // If the variable is 'untagged' (i.e., it has the dummy tag), assign it the appropriate tag.
769 // Otherwise, find the range for which the variable has already been tagged.
770 if (existing_tag != DUMMY_TAG) {
771 bool found_tag = false;
772 for (const auto& r : range_lists) {
773 if (r.second.range_tag == existing_tag) {
774 found_tag = true;
775 if (r.first < target_range) {
776 // The variable already has a more restrictive range check, so do nothing.
777 return;
778 } else {
779 // The range constraint we are trying to impose is more restrictive than the existing range
780 // constraint. It would be difficult to remove an existing range check. Instead deep-copy the
781 // variable and apply a range check to new variable
782 const uint32_t copied_witness = this->add_variable(this->get_variable(variable_index));
783 create_add_gate({ .a = variable_index,
784 .b = copied_witness,
785 .c = this->zero_idx(),
786 .a_scaling = 1,
787 .b_scaling = -1,
788 .c_scaling = 0,
789 .const_scaling = 0 });
790 // Recurse with new witness that has no tag attached.
791 create_new_range_constraint(copied_witness, target_range, msg);
792 return;
793 }
794 }
795 }
796 ASSERT(found_tag);
797 }
798 assign_tag(variable_index, list.range_tag);
799 list.variable_indices.emplace_back(variable_index);
800 }
801}
802
803template <typename ExecutionTrace> void UltraCircuitBuilder_<ExecutionTrace>::process_range_list(RangeList& list)
804{
805 this->assert_valid_variables(list.variable_indices);
806
807 BB_ASSERT_GT(list.variable_indices.size(), 0U);
808
809 // replace witness index in variable_indices with the real variable index i.e. if a copy constraint has been
810 // applied on a variable after it was range constrained, this makes sure the indices in list point to the updated
811 // index in the range list so the set equivalence does not fail
812 for (uint32_t& x : list.variable_indices) {
813 x = this->real_variable_index[x];
814 }
815 // remove duplicate witness indices to prevent the sorted list set size being wrong!
816 std::sort(list.variable_indices.begin(), list.variable_indices.end());
817 auto back_iterator = std::unique(list.variable_indices.begin(), list.variable_indices.end());
818 list.variable_indices.erase(back_iterator, list.variable_indices.end());
819
820 // go over variables
821 // iterate over each variable and create mirror variable with same value - with tau tag
822 // need to make sure that, in original list, increments of at most 3
823 std::vector<uint32_t> sorted_list;
824 sorted_list.reserve(list.variable_indices.size());
825 for (const auto variable_index : list.variable_indices) {
826 const auto& field_element = this->get_variable(variable_index);
827 const uint32_t shrinked_value = (uint32_t)field_element.from_montgomery_form().data[0];
828 sorted_list.emplace_back(shrinked_value);
829 }
830
831#ifdef NO_PAR_ALGOS
832 std::sort(sorted_list.begin(), sorted_list.end());
833#else
834 std::sort(std::execution::par_unseq, sorted_list.begin(), sorted_list.end());
835#endif
836 // list must be padded to a multipe of 4 and larger than 4 (gate_width)
837 constexpr size_t gate_width = NUM_WIRES;
838 size_t padding = (gate_width - (list.variable_indices.size() % gate_width)) % gate_width;
839
840 std::vector<uint32_t> indices;
841 indices.reserve(padding + sorted_list.size());
842
843 if (list.variable_indices.size() <= gate_width) {
844 padding += gate_width;
845 }
846 for (size_t i = 0; i < padding; ++i) {
847 indices.emplace_back(this->zero_idx());
848 }
849 for (const auto sorted_value : sorted_list) {
850 const uint32_t index = this->add_variable(sorted_value);
851 assign_tag(index, list.tau_tag);
852 indices.emplace_back(index);
853 }
854 create_sort_constraint_with_edges(indices, 0, list.target_range);
855}
856
857template <typename ExecutionTrace> void UltraCircuitBuilder_<ExecutionTrace>::process_range_lists()
858{
859 for (auto& i : range_lists) {
860 process_range_list(i.second);
861 }
862}
863
864/*
865 * Create range constraint:
866 * add variable index to a list of range constrained variables
867 * data structures: vector of lists, each list contains:
868 * - the range size
869 * - the list of variables in the range
870 * - a generalized permutation tag
871 *
872 * create range constraint parameters: variable index && range size
873 *
874 * std::map<uint64_t, RangeList> range_lists;
875 */
876// Check for a sequence of variables that neighboring differences are at most 3 (used for batched range checkj)
877template <typename ExecutionTrace>
878void UltraCircuitBuilder_<ExecutionTrace>::create_sort_constraint(const std::vector<uint32_t>& variable_index)
879{
880 constexpr size_t gate_width = NUM_WIRES;
881 BB_ASSERT_EQ(variable_index.size() % gate_width, 0U);
882 this->assert_valid_variables(variable_index);
883
884 for (size_t i = 0; i < variable_index.size(); i += gate_width) {
885 blocks.delta_range.populate_wires(
886 variable_index[i], variable_index[i + 1], variable_index[i + 2], variable_index[i + 3]);
887
888 ++this->num_gates;
889 blocks.delta_range.q_m().emplace_back(0);
890 blocks.delta_range.q_1().emplace_back(0);
891 blocks.delta_range.q_2().emplace_back(0);
892 blocks.delta_range.q_3().emplace_back(0);
893 blocks.delta_range.q_c().emplace_back(0);
894 blocks.delta_range.q_4().emplace_back(0);
895 blocks.delta_range.set_gate_selector(1);
896 check_selector_length_consistency();
897 }
898 // dummy gate needed because of sort widget's check of next row
899 create_unconstrained_gate(blocks.delta_range,
900 variable_index[variable_index.size() - 1],
901 this->zero_idx(),
902 this->zero_idx(),
903 this->zero_idx());
904}
905
906// useful to put variables in the witness that aren't already used - e.g. the dummy variables of the range constraint in
907// multiples of four
908template <typename ExecutionTrace>
909void UltraCircuitBuilder_<ExecutionTrace>::create_unconstrained_gates(const std::vector<uint32_t>& variable_index)
910{
911 std::vector<uint32_t> padded_list = variable_index;
912 constexpr size_t gate_width = NUM_WIRES;
913 const uint64_t padding = (gate_width - (padded_list.size() % gate_width)) % gate_width;
914 for (uint64_t i = 0; i < padding; ++i) {
915 padded_list.emplace_back(this->zero_idx());
916 }
917 this->assert_valid_variables(variable_index);
918 this->assert_valid_variables(padded_list);
919
920 for (size_t i = 0; i < padded_list.size(); i += gate_width) {
921 create_unconstrained_gate(
922 blocks.arithmetic, padded_list[i], padded_list[i + 1], padded_list[i + 2], padded_list[i + 3]);
923 }
924}
925
926// Check for a sequence of variables that neighboring differences are at most 3 (used for batched range checks)
927template <typename ExecutionTrace>
929 const std::vector<uint32_t>& variable_index, const FF& start, const FF& end)
930{
931 // Convenient to assume size is at least 8 (gate_width = 4) for separate gates for start and end conditions
932 constexpr size_t gate_width = NUM_WIRES;
933 BB_ASSERT_EQ(variable_index.size() % gate_width, 0U);
934 BB_ASSERT_GT(variable_index.size(), gate_width);
935 this->assert_valid_variables(variable_index);
936
937 auto& block = blocks.delta_range;
938
939 // Add an arithmetic gate to ensure the first input is equal to the start value of the range being checked
940 create_add_gate({ variable_index[0], this->zero_idx(), this->zero_idx(), 1, 0, 0, -start });
941
942 // enforce range check for all but the final row
943 for (size_t i = 0; i < variable_index.size() - gate_width; i += gate_width) {
944
945 block.populate_wires(variable_index[i], variable_index[i + 1], variable_index[i + 2], variable_index[i + 3]);
946 ++this->num_gates;
947 block.q_m().emplace_back(0);
948 block.q_1().emplace_back(0);
949 block.q_2().emplace_back(0);
950 block.q_3().emplace_back(0);
951 block.q_c().emplace_back(0);
952 block.q_4().emplace_back(0);
953 block.set_gate_selector(1);
954 check_selector_length_consistency();
955 }
956 // enforce range checks of last row and ending at end
957 if (variable_index.size() > gate_width) {
958 block.populate_wires(variable_index[variable_index.size() - 4],
959 variable_index[variable_index.size() - 3],
960 variable_index[variable_index.size() - 2],
961 variable_index[variable_index.size() - 1]);
962 ++this->num_gates;
963 block.q_m().emplace_back(0);
964 block.q_1().emplace_back(0);
965 block.q_2().emplace_back(0);
966 block.q_3().emplace_back(0);
967 block.q_c().emplace_back(0);
968 block.q_4().emplace_back(0);
969 block.set_gate_selector(1);
970 check_selector_length_consistency();
971 }
972
973 // NOTE(https://github.com/AztecProtocol/barretenberg/issues/879): Optimisation opportunity to use a single gate
974 // (and remove dummy gate). This used to be a single gate before trace sorting based on gate types. The dummy gate
975 // has been added to allow the previous gate to access the required wire data via shifts, allowing the arithmetic
976 // gate to occur out of sequence. More details on the linked Github issue.
977 create_unconstrained_gate(
978 block, variable_index[variable_index.size() - 1], this->zero_idx(), this->zero_idx(), this->zero_idx());
979 create_add_gate({ variable_index[variable_index.size() - 1], this->zero_idx(), this->zero_idx(), 1, 0, 0, -end });
980}
981
1004template <typename ExecutionTrace>
1006{
1007 auto& block = blocks.memory;
1008 block.set_gate_selector(type == MEMORY_SELECTORS::MEM_NONE ? 0 : 1);
1009 switch (type) {
1010 case MEMORY_SELECTORS::ROM_CONSISTENCY_CHECK: {
1011 // Memory read gate used with the sorted list of memory reads.
1012 // Apply sorted memory read checks with the following additional check:
1013 // 1. Assert that if index field across two gates does not change, the value field does not change.
1014 // Used for ROM reads and RAM reads across write/read boundaries
1015 block.q_1().emplace_back(1);
1016 block.q_2().emplace_back(1);
1017 block.q_3().emplace_back(0);
1018 block.q_4().emplace_back(0);
1019 block.q_m().emplace_back(0);
1020 block.q_c().emplace_back(0);
1021 check_selector_length_consistency();
1022 break;
1023 }
1024 case MEMORY_SELECTORS::RAM_CONSISTENCY_CHECK: {
1025 // Memory read gate used with the sorted list of memory reads.
1026 // 1. Validate adjacent index values across 2 gates increases by 0 or 1
1027 // 2. Validate record computation (r = read_write_flag + index * \eta + \timestamp * \eta^2 + value * \eta^3)
1028 // 3. If adjacent index values across 2 gates does not change, and the next gate's read_write_flag is set to
1029 // 'read', validate adjacent values do not change Used for ROM reads and RAM reads across read/write boundaries
1030 block.q_1().emplace_back(0);
1031 block.q_2().emplace_back(0);
1032 block.q_3().emplace_back(1);
1033 block.q_4().emplace_back(0);
1034 block.q_m().emplace_back(0);
1035 block.q_c().emplace_back(0);
1036 check_selector_length_consistency();
1037 break;
1038 }
1039 case MEMORY_SELECTORS::RAM_TIMESTAMP_CHECK: {
1040 // For two adjacent RAM entries that share the same index, validate the timestamp value is monotonically
1041 // increasing
1042 block.q_1().emplace_back(1);
1043 block.q_2().emplace_back(0);
1044 block.q_3().emplace_back(0);
1045 block.q_4().emplace_back(1);
1046 block.q_m().emplace_back(0);
1047 block.q_c().emplace_back(0);
1048 check_selector_length_consistency();
1049 break;
1050 }
1051 case MEMORY_SELECTORS::ROM_READ: {
1052 // Memory read gate for reading memory cells.
1053 // Validates record witness computation (r = read_write_flag + index * \eta + timestamp * \eta^2 + value *
1054 // \eta^3)
1055 block.q_1().emplace_back(1);
1056 block.q_2().emplace_back(0);
1057 block.q_3().emplace_back(0);
1058 block.q_4().emplace_back(0);
1059 block.q_m().emplace_back(1); // validate record witness is correctly computed
1060 block.q_c().emplace_back(0); // read/write flag stored in q_c
1061 check_selector_length_consistency();
1062 break;
1063 }
1064 case MEMORY_SELECTORS::RAM_READ: {
1065 // Memory read gate for reading memory cells.
1066 // Validates record witness computation (r = read_write_flag + index * \eta + timestamp * \eta^2 + value *
1067 // \eta^3)
1068 block.q_1().emplace_back(1);
1069 block.q_2().emplace_back(0);
1070 block.q_3().emplace_back(0);
1071 block.q_4().emplace_back(0);
1072 block.q_m().emplace_back(1); // validate record witness is correctly computed
1073 block.q_c().emplace_back(0); // read/write flag stored in q_c
1074 check_selector_length_consistency();
1075 break;
1076 }
1077 case MEMORY_SELECTORS::RAM_WRITE: {
1078 // Memory read gate for writing memory cells.
1079 // Validates record witness computation (r = read_write_flag + index * \eta + timestamp * \eta^2 + value *
1080 // \eta^3)
1081 block.q_1().emplace_back(1);
1082 block.q_2().emplace_back(0);
1083 block.q_3().emplace_back(0);
1084 block.q_4().emplace_back(0);
1085 block.q_m().emplace_back(1); // validate record witness is correctly computed
1086 block.q_c().emplace_back(1); // read/write flag stored in q_c
1087 check_selector_length_consistency();
1088 break;
1089 }
1090 default: {
1091 block.q_1().emplace_back(0);
1092 block.q_2().emplace_back(0);
1093 block.q_3().emplace_back(0);
1094 block.q_4().emplace_back(0);
1095 block.q_m().emplace_back(0);
1096 block.q_c().emplace_back(0);
1097 check_selector_length_consistency();
1098 break;
1099 }
1100 }
1101}
1102
1126template <typename ExecutionTrace>
1128{
1129 auto& block = blocks.nnf;
1130 block.set_gate_selector(type == NNF_SELECTORS::NNF_NONE ? 0 : 1);
1131 switch (type) {
1132 case NNF_SELECTORS::LIMB_ACCUMULATE_1: {
1133 block.q_1().emplace_back(0);
1134 block.q_2().emplace_back(0);
1135 block.q_3().emplace_back(1);
1136 block.q_4().emplace_back(1);
1137 block.q_m().emplace_back(0);
1138 block.q_c().emplace_back(0);
1139 check_selector_length_consistency();
1140 break;
1141 }
1142 case NNF_SELECTORS::LIMB_ACCUMULATE_2: {
1143 block.q_1().emplace_back(0);
1144 block.q_2().emplace_back(0);
1145 block.q_3().emplace_back(1);
1146 block.q_4().emplace_back(0);
1147 block.q_m().emplace_back(1);
1148 block.q_c().emplace_back(0);
1149 check_selector_length_consistency();
1150 break;
1151 }
1152 case NNF_SELECTORS::NON_NATIVE_FIELD_1: {
1153 block.q_1().emplace_back(0);
1154 block.q_2().emplace_back(1);
1155 block.q_3().emplace_back(1);
1156 block.q_4().emplace_back(0);
1157 block.q_m().emplace_back(0);
1158 block.q_c().emplace_back(0);
1159 check_selector_length_consistency();
1160 break;
1161 }
1162 case NNF_SELECTORS::NON_NATIVE_FIELD_2: {
1163 block.q_1().emplace_back(0);
1164 block.q_2().emplace_back(1);
1165 block.q_3().emplace_back(0);
1166 block.q_4().emplace_back(1);
1167 block.q_m().emplace_back(0);
1168 block.q_c().emplace_back(0);
1169 check_selector_length_consistency();
1170 break;
1171 }
1172 case NNF_SELECTORS::NON_NATIVE_FIELD_3: {
1173 block.q_1().emplace_back(0);
1174 block.q_2().emplace_back(1);
1175 block.q_3().emplace_back(0);
1176 block.q_4().emplace_back(0);
1177 block.q_m().emplace_back(1);
1178 block.q_c().emplace_back(0);
1179 check_selector_length_consistency();
1180 break;
1181 }
1182 default: {
1183 block.q_1().emplace_back(0);
1184 block.q_2().emplace_back(0);
1185 block.q_3().emplace_back(0);
1186 block.q_4().emplace_back(0);
1187 block.q_m().emplace_back(0);
1188 block.q_c().emplace_back(0);
1189 check_selector_length_consistency();
1190 break;
1191 }
1192 }
1193}
1194
1205template <typename ExecutionTrace>
1207 const uint32_t hi_idx,
1208 const size_t lo_limb_bits,
1209 const size_t hi_limb_bits,
1210 std::string const& msg)
1211{
1212 // Validate limbs are <= 70 bits. If limbs are larger we require more witnesses and cannot use our limb accumulation
1213 // custom gate
1214 BB_ASSERT_LTE(lo_limb_bits, 14U * 5U);
1215 BB_ASSERT_LTE(hi_limb_bits, 14U * 5U);
1216
1217 // If the value is larger than the range, we log the error in builder
1218 const bool is_lo_out_of_range = (uint256_t(this->get_variable_reference(lo_idx)) >= (uint256_t(1) << lo_limb_bits));
1219 if (is_lo_out_of_range && !this->failed()) {
1220 this->failure(msg + ": lo limb.");
1221 }
1222 const bool is_hi_out_of_range = (uint256_t(this->get_variable_reference(hi_idx)) >= (uint256_t(1) << hi_limb_bits));
1223 if (is_hi_out_of_range && !this->failed()) {
1224 this->failure(msg + ": hi limb.");
1225 }
1226
1227 // Sometimes we try to use limbs that are too large. It's easier to catch this issue here
1228 const auto get_sublimbs = [&](const uint32_t& limb_idx, const std::array<uint64_t, 5>& sublimb_masks) {
1229 const uint256_t limb = this->get_variable(limb_idx);
1230 // we can use constant 2^14 - 1 mask here. If the sublimb value exceeds the expected value then witness will
1231 // fail the range check below
1232 // We also use zero_idx to substitute variables that should be zero
1233 constexpr uint256_t MAX_SUBLIMB_MASK = (uint256_t(1) << 14) - 1;
1234 std::array<uint32_t, 5> sublimb_indices;
1235 sublimb_indices[0] = sublimb_masks[0] != 0 ? this->add_variable(limb & MAX_SUBLIMB_MASK) : this->zero_idx();
1236 sublimb_indices[1] =
1237 sublimb_masks[1] != 0 ? this->add_variable((limb >> 14) & MAX_SUBLIMB_MASK) : this->zero_idx();
1238 sublimb_indices[2] =
1239 sublimb_masks[2] != 0 ? this->add_variable((limb >> 28) & MAX_SUBLIMB_MASK) : this->zero_idx();
1240 sublimb_indices[3] =
1241 sublimb_masks[3] != 0 ? this->add_variable((limb >> 42) & MAX_SUBLIMB_MASK) : this->zero_idx();
1242 sublimb_indices[4] =
1243 sublimb_masks[4] != 0 ? this->add_variable((limb >> 56) & MAX_SUBLIMB_MASK) : this->zero_idx();
1244 return sublimb_indices;
1245 };
1246
1247 const auto get_limb_masks = [](size_t limb_bits) {
1248 std::array<uint64_t, 5> sublimb_masks;
1249 sublimb_masks[0] = limb_bits >= 14 ? 14 : limb_bits;
1250 sublimb_masks[1] = limb_bits >= 28 ? 14 : (limb_bits > 14 ? limb_bits - 14 : 0);
1251 sublimb_masks[2] = limb_bits >= 42 ? 14 : (limb_bits > 28 ? limb_bits - 28 : 0);
1252 sublimb_masks[3] = limb_bits >= 56 ? 14 : (limb_bits > 42 ? limb_bits - 42 : 0);
1253 sublimb_masks[4] = (limb_bits > 56 ? limb_bits - 56 : 0);
1254
1255 for (auto& mask : sublimb_masks) {
1256 mask = (1ULL << mask) - 1ULL;
1257 }
1258 return sublimb_masks;
1259 };
1260
1261 const auto lo_masks = get_limb_masks(lo_limb_bits);
1262 const auto hi_masks = get_limb_masks(hi_limb_bits);
1263 const std::array<uint32_t, 5> lo_sublimbs = get_sublimbs(lo_idx, lo_masks);
1264 const std::array<uint32_t, 5> hi_sublimbs = get_sublimbs(hi_idx, hi_masks);
1265
1266 blocks.nnf.populate_wires(lo_sublimbs[0], lo_sublimbs[1], lo_sublimbs[2], lo_idx);
1267 blocks.nnf.populate_wires(lo_sublimbs[3], lo_sublimbs[4], hi_sublimbs[0], hi_sublimbs[1]);
1268 blocks.nnf.populate_wires(hi_sublimbs[2], hi_sublimbs[3], hi_sublimbs[4], hi_idx);
1269
1270 apply_nnf_selectors(NNF_SELECTORS::LIMB_ACCUMULATE_1);
1271 apply_nnf_selectors(NNF_SELECTORS::LIMB_ACCUMULATE_2);
1272 apply_nnf_selectors(NNF_SELECTORS::NNF_NONE);
1273 this->num_gates += 3;
1274
1275 for (size_t i = 0; i < 5; i++) {
1276 if (lo_masks[i] != 0) {
1277 create_new_range_constraint(lo_sublimbs[i], lo_masks[i], "ultra_circuit_builder: sublimb of low too large");
1278 }
1279 if (hi_masks[i] != 0) {
1280 create_new_range_constraint(hi_sublimbs[i], hi_masks[i], "ultra_circuit_builder: sublimb of hi too large");
1281 }
1282 }
1283};
1284
1295template <typename ExecutionTrace>
1297 const uint32_t limb_idx, const size_t num_limb_bits)
1298{
1299 BB_ASSERT_LT(uint256_t(this->get_variable_reference(limb_idx)), (uint256_t(1) << num_limb_bits));
1300 constexpr FF LIMB_MASK = (uint256_t(1) << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS) - 1;
1301 const uint256_t value = this->get_variable(limb_idx);
1302 const uint256_t low = value & LIMB_MASK;
1303 const uint256_t hi = value >> DEFAULT_NON_NATIVE_FIELD_LIMB_BITS;
1304 BB_ASSERT_EQ(low + (hi << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS), value);
1305
1306 const uint32_t low_idx = this->add_variable(low);
1307 const uint32_t hi_idx = this->add_variable(hi);
1308
1309 BB_ASSERT_GT(num_limb_bits, DEFAULT_NON_NATIVE_FIELD_LIMB_BITS);
1310 const size_t lo_bits = DEFAULT_NON_NATIVE_FIELD_LIMB_BITS;
1311 const size_t hi_bits = num_limb_bits - DEFAULT_NON_NATIVE_FIELD_LIMB_BITS;
1312 range_constrain_two_limbs(
1313 low_idx, hi_idx, lo_bits, hi_bits, "decompose_non_native_field_double_width_limb: limbs too large");
1314
1315 return std::array<uint32_t, 2>{ low_idx, hi_idx };
1316}
1317
1334template <typename ExecutionTrace>
1337{
1338
1340 this->get_variable(input.a[0]),
1341 this->get_variable(input.a[1]),
1342 this->get_variable(input.a[2]),
1343 this->get_variable(input.a[3]),
1344 };
1346 this->get_variable(input.b[0]),
1347 this->get_variable(input.b[1]),
1348 this->get_variable(input.b[2]),
1349 this->get_variable(input.b[3]),
1350 };
1352 this->get_variable(input.q[0]),
1353 this->get_variable(input.q[1]),
1354 this->get_variable(input.q[2]),
1355 this->get_variable(input.q[3]),
1356 };
1358 this->get_variable(input.r[0]),
1359 this->get_variable(input.r[1]),
1360 this->get_variable(input.r[2]),
1361 this->get_variable(input.r[3]),
1362 };
1363 constexpr FF LIMB_SHIFT = uint256_t(1) << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS;
1364 constexpr FF LIMB_RSHIFT = FF(1) / FF(uint256_t(1) << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS);
1365 constexpr FF LIMB_RSHIFT_2 = FF(1) / FF(uint256_t(1) << (2 * DEFAULT_NON_NATIVE_FIELD_LIMB_BITS));
1366
1367 FF lo_0 = a[0] * b[0] - r[0] + (a[1] * b[0] + a[0] * b[1]) * LIMB_SHIFT;
1368 FF lo_1 = (lo_0 + q[0] * input.neg_modulus[0] +
1369 (q[1] * input.neg_modulus[0] + q[0] * input.neg_modulus[1] - r[1]) * LIMB_SHIFT) *
1370 LIMB_RSHIFT_2;
1371
1372 FF hi_0 = a[2] * b[0] + a[0] * b[2] + (a[0] * b[3] + a[3] * b[0] - r[3]) * LIMB_SHIFT;
1373 FF hi_1 = hi_0 + a[1] * b[1] - r[2] + (a[1] * b[2] + a[2] * b[1]) * LIMB_SHIFT;
1374 FF hi_2 = (hi_1 + lo_1 + q[2] * input.neg_modulus[0] +
1375 (q[3] * input.neg_modulus[0] + q[2] * input.neg_modulus[1]) * LIMB_SHIFT);
1376 FF hi_3 = (hi_2 + (q[0] * input.neg_modulus[3] + q[1] * input.neg_modulus[2]) * LIMB_SHIFT +
1377 (q[0] * input.neg_modulus[2] + q[1] * input.neg_modulus[1])) *
1378 LIMB_RSHIFT_2;
1379
1380 const uint32_t lo_0_idx = this->add_variable(lo_0);
1381 const uint32_t lo_1_idx = this->add_variable(lo_1);
1382 const uint32_t hi_0_idx = this->add_variable(hi_0);
1383 const uint32_t hi_1_idx = this->add_variable(hi_1);
1384 const uint32_t hi_2_idx = this->add_variable(hi_2);
1385 const uint32_t hi_3_idx = this->add_variable(hi_3);
1386
1387 // TODO(https://github.com/AztecProtocol/barretenberg/issues/879): Originally this was a single arithmetic gate.
1388 // With trace sorting, we must add a dummy gate since the add gate would otherwise try to read into an nnf gate that
1389 // has been sorted out of sequence.
1390 // product gate 1
1391 // (lo_0 + q_0(p_0 + p_1*2^b) + q_1(p_0*2^b) - (r_1)2^b)2^-2b - lo_1 = 0
1392 create_big_add_gate({ input.q[0],
1393 input.q[1],
1394 input.r[1],
1395 lo_1_idx,
1396 input.neg_modulus[0] + input.neg_modulus[1] * LIMB_SHIFT,
1397 input.neg_modulus[0] * LIMB_SHIFT,
1398 -LIMB_SHIFT,
1399 -LIMB_SHIFT.sqr(),
1400 0 },
1401 true);
1402 create_unconstrained_gate(blocks.arithmetic, this->zero_idx(), this->zero_idx(), this->zero_idx(), lo_0_idx);
1403 //
1404 // a = (a3 || a2 || a1 || a0) = (a3 * 2^b + a2) * 2^b + (a1 * 2^b + a0)
1405 // b = (b3 || b2 || b1 || b0) = (b3 * 2^b + b2) * 2^b + (b1 * 2^b + b0)
1406 //
1407 // Check if lo_0 was computed correctly.
1408 // The gate structure for the nnf gates is as follows:
1409 //
1410 // | a1 | b1 | r0 | lo_0 | <-- product gate 1: check lo_0
1411 // | a0 | b0 | a3 | b3 |
1412 // | a2 | b2 | r3 | hi_0 |
1413 // | a1 | b1 | r2 | hi_1 |
1414 //
1415 // Constaint: lo_0 = (a1 * b0 + a0 * b1) * 2^b + (a0 * b0) - r0
1416 // w4 = (w1 * w'2 + w'1 * w2) * 2^b + (w'1 * w'2) - w3
1417 //
1418 blocks.nnf.populate_wires(input.a[1], input.b[1], input.r[0], lo_0_idx);
1419 apply_nnf_selectors(NNF_SELECTORS::NON_NATIVE_FIELD_1);
1420 ++this->num_gates;
1421
1422 //
1423 // Check if hi_0 was computed correctly.
1424 //
1425 // | a1 | b1 | r0 | lo_0 |
1426 // | a0 | b0 | a3 | b3 | <-- product gate 2: check hi_0
1427 // | a2 | b2 | r3 | hi_0 |
1428 // | a1 | b1 | r2 | hi_1 |
1429 //
1430 // Constaint: hi_0 = (a0 * b3 + a3 * b0 - r3) * 2^b + (a0 * b2 + a2 * b0) - r2
1431 // w'4 = (w1 * w4 + w2 * w3 - w'3) * 2^b + (w1 * w'2 + w'1 * w2) - w'3
1432 //
1433 blocks.nnf.populate_wires(input.a[0], input.b[0], input.a[3], input.b[3]);
1434 apply_nnf_selectors(NNF_SELECTORS::NON_NATIVE_FIELD_2);
1435 ++this->num_gates;
1436
1437 //
1438 // Check if hi_1 was computed correctly.
1439 //
1440 // | a1 | b1 | r0 | lo_0 |
1441 // | a0 | b0 | a3 | b3 |
1442 // | a2 | b2 | r3 | hi_0 | <-- product gate 3: check hi_1
1443 // | a1 | b1 | r2 | hi_1 |
1444 //
1445 // Constaint: hi_1 = hi_0 + (a2 * b1 + a1 * b2) * 2^b + (a1 * b1)
1446 // w'4 = w4 + (w1 * w'2 + w'1 * w2) * 2^b + (w'1 * w'2)
1447 //
1448 blocks.nnf.populate_wires(input.a[2], input.b[2], input.r[3], hi_0_idx);
1449 apply_nnf_selectors(NNF_SELECTORS::NON_NATIVE_FIELD_3);
1450 ++this->num_gates;
1451
1452 //
1453 // Does nothing, but is used by the previous gate to read the hi_1 limb.
1454 //
1455 blocks.nnf.populate_wires(input.a[1], input.b[1], input.r[2], hi_1_idx);
1456 apply_nnf_selectors(NNF_SELECTORS::NNF_NONE);
1457 ++this->num_gates;
1458
1465 create_big_add_gate(
1466 {
1467 input.q[2],
1468 input.q[3],
1469 lo_1_idx,
1470 hi_1_idx,
1471 -input.neg_modulus[1] * LIMB_SHIFT - input.neg_modulus[0],
1472 -input.neg_modulus[0] * LIMB_SHIFT,
1473 -1,
1474 -1,
1475 0,
1476 },
1477 true);
1478
1484 create_big_add_gate({
1485 hi_3_idx,
1486 input.q[0],
1487 input.q[1],
1488 hi_2_idx,
1489 -1,
1490 input.neg_modulus[3] * LIMB_RSHIFT + input.neg_modulus[2] * LIMB_RSHIFT_2,
1491 input.neg_modulus[2] * LIMB_RSHIFT + input.neg_modulus[1] * LIMB_RSHIFT_2,
1492 LIMB_RSHIFT_2,
1493 0,
1494 });
1495
1496 return std::array<uint32_t, 2>{ lo_1_idx, hi_3_idx };
1497}
1498
1504{
1505 BB_BENCH_NAME("populate_public_inputs_block");
1506
1507 // Update the public inputs block
1508 for (const auto& idx : this->public_inputs()) {
1509 // first two wires get a copy of the public inputs
1510 blocks.pub_inputs.populate_wires(idx, idx, this->zero_idx(), this->zero_idx());
1511 for (auto& selector : this->blocks.pub_inputs.get_selectors()) {
1512 selector.emplace_back(0);
1513 }
1514 }
1515}
1516
1523{
1524 for (size_t i = 0; i < cached_partial_non_native_field_multiplications.size(); ++i) {
1525 auto& c = cached_partial_non_native_field_multiplications[i];
1526 for (size_t j = 0; j < c.a.size(); ++j) {
1527 c.a[j] = this->real_variable_index[c.a[j]];
1528 c.b[j] = this->real_variable_index[c.b[j]];
1529 }
1530 }
1531 cached_partial_non_native_field_multiplication::deduplicate(cached_partial_non_native_field_multiplications, this);
1532
1533 // iterate over the cached items and create constraints
1534 for (const auto& input : cached_partial_non_native_field_multiplications) {
1535
1536 blocks.nnf.populate_wires(input.a[1], input.b[1], this->zero_idx(), input.lo_0);
1537 apply_nnf_selectors(NNF_SELECTORS::NON_NATIVE_FIELD_1);
1538 ++this->num_gates;
1539
1540 blocks.nnf.populate_wires(input.a[0], input.b[0], input.a[3], input.b[3]);
1541 apply_nnf_selectors(NNF_SELECTORS::NON_NATIVE_FIELD_2);
1542 ++this->num_gates;
1543
1544 blocks.nnf.populate_wires(input.a[2], input.b[2], this->zero_idx(), input.hi_0);
1545 apply_nnf_selectors(NNF_SELECTORS::NON_NATIVE_FIELD_3);
1546 ++this->num_gates;
1547
1548 blocks.nnf.populate_wires(input.a[1], input.b[1], this->zero_idx(), input.hi_1);
1549 apply_nnf_selectors(NNF_SELECTORS::NNF_NONE);
1550 ++this->num_gates;
1551 }
1552}
1553
1562template <typename ExecutionTrace>
1565{
1566
1568 this->get_variable(input.a[0]),
1569 this->get_variable(input.a[1]),
1570 this->get_variable(input.a[2]),
1571 this->get_variable(input.a[3]),
1572 };
1574 this->get_variable(input.b[0]),
1575 this->get_variable(input.b[1]),
1576 this->get_variable(input.b[2]),
1577 this->get_variable(input.b[3]),
1578 };
1579
1580 constexpr FF LIMB_SHIFT = uint256_t(1) << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS;
1581
1582 FF lo_0 = a[0] * b[0] + (a[1] * b[0] + a[0] * b[1]) * LIMB_SHIFT;
1583
1584 FF hi_0 = a[2] * b[0] + a[0] * b[2] + (a[0] * b[3] + a[3] * b[0]) * LIMB_SHIFT;
1585 FF hi_1 = hi_0 + a[1] * b[1] + (a[1] * b[2] + a[2] * b[1]) * LIMB_SHIFT;
1586
1587 const uint32_t lo_0_idx = this->add_variable(lo_0);
1588 const uint32_t hi_0_idx = this->add_variable(hi_0);
1589 const uint32_t hi_1_idx = this->add_variable(hi_1);
1590
1591 // Add witnesses into the multiplication cache
1592 // (when finalising the circuit, we will remove duplicates; several dups produced by biggroup.hpp methods)
1594 .a = input.a,
1595 .b = input.b,
1596 .lo_0 = lo_0_idx,
1597 .hi_0 = hi_0_idx,
1598 .hi_1 = hi_1_idx,
1599 };
1600 cached_partial_non_native_field_multiplications.emplace_back(cache_entry);
1601 return std::array<uint32_t, 2>{ lo_0_idx, hi_1_idx };
1602}
1603
1609template <typename ExecutionTrace>
1612{
1613 const auto& x_0 = std::get<0>(limb0).first;
1614 const auto& x_1 = std::get<0>(limb1).first;
1615 const auto& x_2 = std::get<0>(limb2).first;
1616 const auto& x_3 = std::get<0>(limb3).first;
1617 const auto& x_p = std::get<0>(limbp);
1618
1619 const auto& x_mulconst0 = std::get<0>(limb0).second;
1620 const auto& x_mulconst1 = std::get<0>(limb1).second;
1621 const auto& x_mulconst2 = std::get<0>(limb2).second;
1622 const auto& x_mulconst3 = std::get<0>(limb3).second;
1623
1624 const auto& y_0 = std::get<1>(limb0).first;
1625 const auto& y_1 = std::get<1>(limb1).first;
1626 const auto& y_2 = std::get<1>(limb2).first;
1627 const auto& y_3 = std::get<1>(limb3).first;
1628 const auto& y_p = std::get<1>(limbp);
1629
1630 const auto& y_mulconst0 = std::get<1>(limb0).second;
1631 const auto& y_mulconst1 = std::get<1>(limb1).second;
1632 const auto& y_mulconst2 = std::get<1>(limb2).second;
1633 const auto& y_mulconst3 = std::get<1>(limb3).second;
1634
1635 // constant additive terms
1636 const auto& addconst0 = std::get<2>(limb0);
1637 const auto& addconst1 = std::get<2>(limb1);
1638 const auto& addconst2 = std::get<2>(limb2);
1639 const auto& addconst3 = std::get<2>(limb3);
1640 const auto& addconstp = std::get<2>(limbp);
1641
1642 // get value of result limbs
1643 const auto z_0value = this->get_variable(x_0) * x_mulconst0 + this->get_variable(y_0) * y_mulconst0 + addconst0;
1644 const auto z_1value = this->get_variable(x_1) * x_mulconst1 + this->get_variable(y_1) * y_mulconst1 + addconst1;
1645 const auto z_2value = this->get_variable(x_2) * x_mulconst2 + this->get_variable(y_2) * y_mulconst2 + addconst2;
1646 const auto z_3value = this->get_variable(x_3) * x_mulconst3 + this->get_variable(y_3) * y_mulconst3 + addconst3;
1647 const auto z_pvalue = this->get_variable(x_p) + this->get_variable(y_p) + addconstp;
1648
1649 const auto z_0 = this->add_variable(z_0value);
1650 const auto z_1 = this->add_variable(z_1value);
1651 const auto z_2 = this->add_variable(z_2value);
1652 const auto z_3 = this->add_variable(z_3value);
1653 const auto z_p = this->add_variable(z_pvalue);
1654
1668 // GATE 1
1669 // | 1 | 2 | 3 | 4 |
1670 // |-----|-----|-----|-----|
1671 // | y.p | x.0 | y.0 | z.p | (b.p + b.p - c.p = 0) AND (a.0 + b.0 - c.0 = 0)
1672 // | x.p | x.1 | y.1 | z.0 | (a.1 + b.1 - c.1 = 0)
1673 // | x.2 | y.2 | z.2 | z.1 | (a.2 + b.2 - c.2 = 0)
1674 // | x.3 | y.3 | z.3 | --- | (a.3 + b.3 - c.3 = 0)
1675 // TODO(https://github.com/AztecProtocol/barretenberg/issues/896): descrepency between above comment and the actual
1676 // implementation below.
1677 auto& block = blocks.arithmetic;
1678 block.populate_wires(y_p, x_0, y_0, x_p);
1679 block.populate_wires(z_p, x_1, y_1, z_0);
1680 block.populate_wires(x_2, y_2, z_2, z_1);
1681 block.populate_wires(x_3, y_3, z_3, this->zero_idx());
1682
1683 block.q_m().emplace_back(addconstp);
1684 block.q_1().emplace_back(0);
1685 block.q_2().emplace_back(-x_mulconst0 *
1686 2); // scale constants by 2. If q_arith = 3 then w_4_omega value (z0) gets scaled by 2x
1687 block.q_3().emplace_back(-y_mulconst0 * 2); // z_0 - (x_0 * -xmulconst0) - (y_0 * ymulconst0) = 0 => z_0 = x_0 + y_0
1688 block.q_4().emplace_back(0);
1689 block.q_c().emplace_back(-addconst0 * 2);
1690 block.set_gate_selector(3);
1691
1692 block.q_m().emplace_back(0);
1693 block.q_1().emplace_back(0);
1694 block.q_2().emplace_back(-x_mulconst1);
1695 block.q_3().emplace_back(-y_mulconst1);
1696 block.q_4().emplace_back(0);
1697 block.q_c().emplace_back(-addconst1);
1698 block.set_gate_selector(2);
1699
1700 block.q_m().emplace_back(0);
1701 block.q_1().emplace_back(-x_mulconst2);
1702 block.q_2().emplace_back(-y_mulconst2);
1703 block.q_3().emplace_back(1);
1704 block.q_4().emplace_back(0);
1705 block.q_c().emplace_back(-addconst2);
1706 block.set_gate_selector(1);
1707
1708 block.q_m().emplace_back(0);
1709 block.q_1().emplace_back(-x_mulconst3);
1710 block.q_2().emplace_back(-y_mulconst3);
1711 block.q_3().emplace_back(1);
1712 block.q_4().emplace_back(0);
1713 block.q_c().emplace_back(-addconst3);
1714 block.set_gate_selector(1);
1715
1716 check_selector_length_consistency();
1717
1718 this->num_gates += 4;
1720 z_0, z_1, z_2, z_3, z_p,
1721 };
1722}
1723
1724template <typename ExecutionTrace>
1727{
1728 const auto& x_0 = std::get<0>(limb0).first;
1729 const auto& x_1 = std::get<0>(limb1).first;
1730 const auto& x_2 = std::get<0>(limb2).first;
1731 const auto& x_3 = std::get<0>(limb3).first;
1732 const auto& x_p = std::get<0>(limbp);
1733
1734 const auto& x_mulconst0 = std::get<0>(limb0).second;
1735 const auto& x_mulconst1 = std::get<0>(limb1).second;
1736 const auto& x_mulconst2 = std::get<0>(limb2).second;
1737 const auto& x_mulconst3 = std::get<0>(limb3).second;
1738
1739 const auto& y_0 = std::get<1>(limb0).first;
1740 const auto& y_1 = std::get<1>(limb1).first;
1741 const auto& y_2 = std::get<1>(limb2).first;
1742 const auto& y_3 = std::get<1>(limb3).first;
1743 const auto& y_p = std::get<1>(limbp);
1744
1745 const auto& y_mulconst0 = std::get<1>(limb0).second;
1746 const auto& y_mulconst1 = std::get<1>(limb1).second;
1747 const auto& y_mulconst2 = std::get<1>(limb2).second;
1748 const auto& y_mulconst3 = std::get<1>(limb3).second;
1749
1750 // constant additive terms
1751 const auto& addconst0 = std::get<2>(limb0);
1752 const auto& addconst1 = std::get<2>(limb1);
1753 const auto& addconst2 = std::get<2>(limb2);
1754 const auto& addconst3 = std::get<2>(limb3);
1755 const auto& addconstp = std::get<2>(limbp);
1756
1757 // get value of result limbs
1758 const auto z_0value = this->get_variable(x_0) * x_mulconst0 - this->get_variable(y_0) * y_mulconst0 + addconst0;
1759 const auto z_1value = this->get_variable(x_1) * x_mulconst1 - this->get_variable(y_1) * y_mulconst1 + addconst1;
1760 const auto z_2value = this->get_variable(x_2) * x_mulconst2 - this->get_variable(y_2) * y_mulconst2 + addconst2;
1761 const auto z_3value = this->get_variable(x_3) * x_mulconst3 - this->get_variable(y_3) * y_mulconst3 + addconst3;
1762 const auto z_pvalue = this->get_variable(x_p) - this->get_variable(y_p) + addconstp;
1763
1764 const auto z_0 = this->add_variable(z_0value);
1765 const auto z_1 = this->add_variable(z_1value);
1766 const auto z_2 = this->add_variable(z_2value);
1767 const auto z_3 = this->add_variable(z_3value);
1768 const auto z_p = this->add_variable(z_pvalue);
1769
1782 // GATE 1
1783 // | 1 | 2 | 3 | 4 |
1784 // |-----|-----|-----|-----|
1785 // | y.p | x.0 | y.0 | z.p | (b.p + c.p - a.p = 0) AND (a.0 - b.0 - c.0 = 0)
1786 // | x.p | x.1 | y.1 | z.0 | (a.1 - b.1 - c.1 = 0)
1787 // | x.2 | y.2 | z.2 | z.1 | (a.2 - b.2 - c.2 = 0)
1788 // | x.3 | y.3 | z.3 | --- | (a.3 - b.3 - c.3 = 0)
1789 auto& block = blocks.arithmetic;
1790 block.populate_wires(y_p, x_0, y_0, z_p);
1791 block.populate_wires(x_p, x_1, y_1, z_0);
1792 block.populate_wires(x_2, y_2, z_2, z_1);
1793 block.populate_wires(x_3, y_3, z_3, this->zero_idx());
1794
1795 block.q_m().emplace_back(-addconstp);
1796 block.q_1().emplace_back(0);
1797 block.q_2().emplace_back(-x_mulconst0 * 2);
1798 block.q_3().emplace_back(y_mulconst0 * 2); // z_0 + (x_0 * -xmulconst0) + (y_0 * ymulconst0) = 0 => z_0 = x_0 - y_0
1799 block.q_4().emplace_back(0);
1800 block.q_c().emplace_back(-addconst0 * 2);
1801 block.set_gate_selector(3);
1802
1803 block.q_m().emplace_back(0);
1804 block.q_1().emplace_back(0);
1805 block.q_2().emplace_back(-x_mulconst1);
1806 block.q_3().emplace_back(y_mulconst1);
1807 block.q_4().emplace_back(0);
1808 block.q_c().emplace_back(-addconst1);
1809 block.set_gate_selector(2);
1810
1811 block.q_m().emplace_back(0);
1812 block.q_1().emplace_back(-x_mulconst2);
1813 block.q_2().emplace_back(y_mulconst2);
1814 block.q_3().emplace_back(1);
1815 block.q_4().emplace_back(0);
1816 block.q_c().emplace_back(-addconst2);
1817 block.set_gate_selector(1);
1818
1819 block.q_m().emplace_back(0);
1820 block.q_1().emplace_back(-x_mulconst3);
1821 block.q_2().emplace_back(y_mulconst3);
1822 block.q_3().emplace_back(1);
1823 block.q_4().emplace_back(0);
1824 block.q_c().emplace_back(-addconst3);
1825 block.set_gate_selector(1);
1826
1827 check_selector_length_consistency();
1828
1829 this->num_gates += 4;
1831 z_0, z_1, z_2, z_3, z_p,
1832 };
1833}
1834
1845template <typename ExecutionTrace>
1847{
1848 return this->rom_ram_logic.create_ROM_array(array_size);
1849}
1850
1861template <typename ExecutionTrace>
1863{
1864 return this->rom_ram_logic.create_RAM_array(array_size);
1865}
1866
1874template <typename ExecutionTrace>
1876 const size_t index_value,
1877 const uint32_t value_witness)
1878{
1879 this->rom_ram_logic.init_RAM_element(this, ram_id, index_value, value_witness);
1880}
1881
1882template <typename ExecutionTrace>
1883uint32_t UltraCircuitBuilder_<ExecutionTrace>::read_RAM_array(const size_t ram_id, const uint32_t index_witness)
1884{
1885 return this->rom_ram_logic.read_RAM_array(this, ram_id, index_witness);
1886}
1887
1888template <typename ExecutionTrace>
1890 const uint32_t index_witness,
1891 const uint32_t value_witness)
1892{
1893 this->rom_ram_logic.write_RAM_array(this, ram_id, index_witness, value_witness);
1894}
1895
1910template <typename ExecutionTrace>
1912 const size_t index_value,
1913 const uint32_t value_witness)
1914{
1915 this->rom_ram_logic.set_ROM_element(this, rom_id, index_value, value_witness);
1916}
1917
1925template <typename ExecutionTrace>
1927 const size_t index_value,
1928 const std::array<uint32_t, 2>& value_witnesses)
1929{
1930 this->rom_ram_logic.set_ROM_element_pair(this, rom_id, index_value, value_witnesses);
1931}
1932
1940template <typename ExecutionTrace>
1941uint32_t UltraCircuitBuilder_<ExecutionTrace>::read_ROM_array(const size_t rom_id, const uint32_t index_witness)
1942{
1943 return this->rom_ram_logic.read_ROM_array(this, rom_id, index_witness);
1944}
1945
1953template <typename ExecutionTrace>
1954std::array<uint32_t, 2> UltraCircuitBuilder_<ExecutionTrace>::read_ROM_array_pair(const size_t rom_id,
1955 const uint32_t index_witness)
1956{
1957 return this->rom_ram_logic.read_ROM_array_pair(this, rom_id, index_witness);
1958}
1959
1963template <typename FF>
1965{
1966 auto& block = this->blocks.poseidon2_external;
1967 block.populate_wires(in.a, in.b, in.c, in.d);
1968 block.q_m().emplace_back(0);
1972 block.q_c().emplace_back(0);
1974 block.set_gate_selector(1);
1975 this->check_selector_length_consistency();
1976 ++this->num_gates;
1977}
1978
1982template <typename FF>
1984{
1985 auto& block = this->blocks.poseidon2_internal;
1986 block.populate_wires(in.a, in.b, in.c, in.d);
1987 block.q_m().emplace_back(0);
1989 block.q_2().emplace_back(0);
1990 block.q_3().emplace_back(0);
1991 block.q_c().emplace_back(0);
1992 block.q_4().emplace_back(0);
1993 block.set_gate_selector(1);
1994 this->check_selector_length_consistency();
1995 ++this->num_gates;
1996}
1997
2004template <typename ExecutionTrace> msgpack::sbuffer UltraCircuitBuilder_<ExecutionTrace>::export_circuit()
2005{
2006 // You should not name `zero` by yourself
2007 // but it will be rewritten anyway
2008 auto first_zero_idx = this->get_first_variable_in_class(this->zero_idx());
2009 if (!this->variable_names.contains(first_zero_idx)) {
2010 this->set_variable_name(this->zero_idx(), "zero");
2011 } else {
2012 this->variable_names[first_zero_idx] = "zero";
2013 }
2014 using base = CircuitBuilderBase<FF>;
2016
2017 std::array<uint64_t, 4> modulus = {
2018 FF::Params::modulus_0, FF::Params::modulus_1, FF::Params::modulus_2, FF::Params::modulus_3
2019 };
2020 std::stringstream buf;
2021 buf << std::hex << std::setfill('0') << std::setw(16) << modulus[3] << std::setw(16) << modulus[2] << std::setw(16)
2022 << modulus[1] << std::setw(16) << modulus[0];
2023
2024 cir.modulus = buf.str();
2025
2026 for (uint32_t i = 0; i < this->num_public_inputs(); i++) {
2027 cir.public_inps.push_back(this->real_variable_index[this->public_inputs()[i]]);
2028 }
2029
2030 for (auto& tup : base::variable_names) {
2031 cir.vars_of_interest.insert({ this->real_variable_index[tup.first], tup.second });
2032 }
2033
2034 for (const auto& var : this->get_variables()) {
2035 cir.variables.push_back(var);
2036 }
2037
2038 FF curve_b;
2039 if constexpr (FF::modulus == bb::fq::modulus) {
2040 curve_b = bb::g1::curve_b;
2041 } else if constexpr (FF::modulus == grumpkin::fq::modulus) {
2042 curve_b = grumpkin::g1::curve_b;
2043 } else {
2044 curve_b = 0;
2045 }
2046
2047 for (auto& block : blocks.get()) {
2048 std::vector<std::vector<FF>> block_selectors;
2050 for (size_t idx = 0; idx < block.size(); ++idx) {
2051 std::vector<FF> tmp_sel = { block.q_m()[idx],
2052 block.q_1()[idx],
2053 block.q_2()[idx],
2054 block.q_3()[idx],
2055 block.q_4()[idx],
2056 block.q_c()[idx],
2057 block.q_arith()[idx],
2058 block.q_delta_range()[idx],
2059 block.q_elliptic()[idx],
2060 block.q_memory()[idx],
2061 block.q_nnf()[idx],
2062 block.q_lookup_type()[idx],
2063 curve_b };
2064
2065 std::vector<uint32_t> tmp_w = {
2066 this->real_variable_index[block.w_l()[idx]],
2067 this->real_variable_index[block.w_r()[idx]],
2068 this->real_variable_index[block.w_o()[idx]],
2069 this->real_variable_index[block.w_4()[idx]],
2070 };
2071
2072 if (idx < block.size() - 1) {
2073 tmp_w.push_back(block.w_l()[idx + 1]);
2074 tmp_w.push_back(block.w_r()[idx + 1]);
2075 tmp_w.push_back(block.w_o()[idx + 1]);
2076 tmp_w.push_back(block.w_4()[idx + 1]);
2077 } else {
2078 tmp_w.push_back(0);
2079 tmp_w.push_back(0);
2080 tmp_w.push_back(0);
2081 tmp_w.push_back(0);
2082 }
2083
2084 block_selectors.push_back(tmp_sel);
2085 block_wires.push_back(tmp_w);
2086 }
2087 cir.selectors.push_back(block_selectors);
2088 cir.wires.push_back(block_wires);
2089 }
2090
2091 cir.real_variable_index = this->real_variable_index;
2092
2093 for (const auto& table : this->lookup_tables) {
2094 const FF table_index(table.table_index);
2095 info("Table no: ", table.table_index);
2096 std::vector<std::vector<FF>> tmp_table;
2097 for (size_t i = 0; i < table.size(); ++i) {
2098 tmp_table.push_back({ table.column_1[i], table.column_2[i], table.column_3[i] });
2099 }
2100 cir.lookup_tables.push_back(tmp_table);
2101 }
2102
2103 cir.real_variable_tags = this->real_variable_tags;
2104
2105 for (const auto& list : range_lists) {
2106 cir.range_tags[list.second.range_tag] = list.first;
2107 }
2108
2109 for (auto& rom_table : this->rom_ram_logic.rom_arrays) {
2110 std::sort(rom_table.records.begin(), rom_table.records.end());
2111
2113 table.reserve(rom_table.records.size());
2114 for (const auto& rom_entry : rom_table.records) {
2115 table.push_back({
2116 this->real_variable_index[rom_entry.index_witness],
2117 this->real_variable_index[rom_entry.value_column1_witness],
2118 this->real_variable_index[rom_entry.value_column2_witness],
2119 });
2120 }
2121 cir.rom_records.push_back(table);
2122 cir.rom_states.push_back(rom_table.state);
2123 }
2124
2125 for (auto& ram_table : this->rom_ram_logic.ram_arrays) {
2126 std::sort(ram_table.records.begin(), ram_table.records.end());
2127
2129 table.reserve(ram_table.records.size());
2130 for (const auto& ram_entry : ram_table.records) {
2131 table.push_back({ this->real_variable_index[ram_entry.index_witness],
2132 this->real_variable_index[ram_entry.value_witness],
2133 this->real_variable_index[ram_entry.timestamp_witness],
2134 ram_entry.access_type });
2135 }
2136 cir.ram_records.push_back(table);
2137 cir.ram_states.push_back(ram_table.state);
2138 }
2139
2140 cir.circuit_finalized = this->circuit_finalized;
2141
2142 msgpack::sbuffer buffer;
2143 msgpack::pack(buffer, cir);
2144 return buffer;
2145}
2146
2149
2150} // namespace bb
#define BB_ASSERT_GT(left, right,...)
Definition assert.hpp:118
#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
#define BB_BENCH_NAME(name)
Definition bb_bench.hpp:218
void create_add_gate(const add_triple_< FF > &in) override
Create an addition gate, where in.a * in.a_scaling + in.b * in.b_scaling + in.c * in....
void fix_witness(const uint32_t witness_index, const FF &witness_value)
Add a gate equating a particular witness to a constant, fixing its value.
void init_RAM_element(const size_t ram_id, const size_t index_value, const uint32_t value_witness)
Initialize a RAM cell to equal value_witness
void create_ecc_dbl_gate(const ecc_dbl_gate_< FF > &in)
Create an elliptic curve doubling gate.
void add_gates_to_ensure_all_polys_are_non_zero()
Ensure all polynomials have at least one non-zero coefficient to avoid commiting to the zero-polynomi...
void process_range_list(RangeList &list)
void create_poseidon2_internal_gate(const poseidon2_internal_gate_< FF > &in)
Poseidon2 internal round gate, activates the q_poseidon2_internal selector and relation.
size_t create_RAM_array(const size_t array_size)
Create a new updatable memory region.
void create_big_mul_gate(const mul_quad_< FF > &in)
Create a basic multiplication gate q_m * a * b + q_1 * a + q_2 * b + q_3 * c + q_4 * d + q_c = 0 (q_a...
void create_big_mul_add_gate(const mul_quad_< FF > &in, const bool use_next_gate_w_4=false)
Create a big multiplication-addition gate, where in.a * in.b * in.mul_scaling + in....
std::vector< uint32_t > decompose_into_default_range(const uint32_t variable_index, const uint64_t num_bits, const uint64_t target_range_bitnum=DEFAULT_PLOOKUP_RANGE_BITNUM, std::string const &msg="decompose_into_default_range")
msgpack::sbuffer export_circuit() override
void create_sort_constraint_with_edges(const std::vector< uint32_t > &variable_index, const FF &, const FF &)
std::tuple< scaled_witness, scaled_witness, FF > add_simple
uint32_t read_RAM_array(const size_t ram_id, const uint32_t index_witness)
void create_unconstrained_gates(const std::vector< uint32_t > &variable_index)
void create_big_add_gate(const add_quad_< FF > &in, const bool use_next_gate_w_4=false)
Create a big addition gate, where in.a * in.a_scaling + in.b * in.b_scaling + in.c * in....
typename ExecutionTrace::FF FF
std::array< uint32_t, 5 > evaluate_non_native_field_addition(add_simple limb0, add_simple limb1, add_simple limb2, add_simple limb3, std::tuple< uint32_t, uint32_t, FF > limbp)
size_t create_ROM_array(const size_t array_size)
Create a new read-only memory region.
plookup::ReadData< uint32_t > create_gates_from_plookup_accumulators(const plookup::MultiTableId &id, const plookup::ReadData< FF > &read_values, const uint32_t key_a_index, std::optional< uint32_t > key_b_index=std::nullopt)
Perform a series of lookups, one for each 'row' in read_values.
std::array< uint32_t, 2 > decompose_non_native_field_double_width_limb(const uint32_t limb_idx, const size_t num_limb_bits=(2 *DEFAULT_NON_NATIVE_FIELD_LIMB_BITS))
Decompose a single witness into two, where the lowest is DEFAULT_NON_NATIVE_FIELD_LIMB_BITS (68) rang...
plookup::BasicTable & get_table(const plookup::BasicTableId id)
Get the basic table with provided ID from the set of tables for the present circuit; create it if it ...
void apply_nnf_selectors(const NNF_SELECTORS type)
Enable the nnf gate of particular type.
void create_bool_gate(const uint32_t a) override
Generate an arithmetic gate equivalent to x^2 - x = 0, which forces x to be 0 or 1.
void create_ecc_add_gate(const ecc_add_gate_< FF > &in)
Create an elliptic curve addition gate.
void finalize_circuit(const bool ensure_nonzero)
void create_sort_constraint(const std::vector< uint32_t > &variable_index)
void create_poly_gate(const poly_triple_< FF > &in) override
A plonk gate with disabled (set to zero) fourth wire. q_m * a * b + q_1 * a + q_2 * b + q_3.
void create_poseidon2_external_gate(const poseidon2_external_gate_< FF > &in)
Poseidon2 external round gate, activates the q_poseidon2_external selector and relation.
std::array< uint32_t, 2 > evaluate_non_native_field_multiplication(const non_native_multiplication_witnesses< FF > &input)
Queue up non-native field multiplication data.
void populate_public_inputs_block()
Copy the public input idx data into the public inputs trace block.
uint32_t read_ROM_array(const size_t rom_id, const uint32_t index_witness)
Read a single element from ROM.
RangeList create_range_list(const uint64_t target_range)
uint32_t put_constant_variable(const FF &variable)
void set_ROM_element(const size_t rom_id, const size_t index_value, const uint32_t value_witness)
Initialize a rom cell to equal value_witness
void create_mul_gate(const mul_triple_< FF > &in) override
Create a multiplication gate with q_m * a * b + q_3 * c + q_const = 0.
void write_RAM_array(const size_t ram_id, const uint32_t index_witness, const uint32_t value_witness)
void set_ROM_element_pair(const size_t rom_id, const size_t index_value, const std::array< uint32_t, 2 > &value_witnesses)
Initialize a ROM array element with a pair of witness values.
std::array< uint32_t, 2 > read_ROM_array_pair(const size_t rom_id, const uint32_t index_witness)
Read a pair of elements from ROM.
void range_constrain_two_limbs(const uint32_t lo_idx, const uint32_t hi_idx, const size_t lo_limb_bits=DEFAULT_NON_NATIVE_FIELD_LIMB_BITS, const size_t hi_limb_bits=DEFAULT_NON_NATIVE_FIELD_LIMB_BITS, std::string const &msg="range_constrain_two_limbs")
std::array< uint32_t, 2 > queue_partial_non_native_field_multiplication(const non_native_partial_multiplication_witnesses< FF > &input)
std::array< uint32_t, 5 > evaluate_non_native_field_subtraction(add_simple limb0, add_simple limb1, add_simple limb2, add_simple limb3, std::tuple< uint32_t, uint32_t, FF > limbp)
void apply_memory_selectors(const MEMORY_SELECTORS type)
Enable the memory gate of particular type.
void process_non_native_field_multiplications()
Called in compute_prover_instance when finalizing circuit. Iterates over the cached_non_native_field_...
void create_new_range_constraint(const uint32_t variable_index, const uint64_t target_range, std::string const msg="create_new_range_constraint")
Constrain a variable to a range.
static constexpr Fq curve_b
Definition group.hpp:51
constexpr uint64_t get_msb() const
Container type for lookup table reads.
Definition types.hpp:357
std::vector< BasicTable::LookupEntry > lookup_entries
Definition types.hpp:363
void info(Args... args)
Definition log.hpp:74
FF a
FF b
uint8_t const * buf
Definition data_store.hpp:9
uint8_t buffer[RANDOM_BUFFER_SIZE]
Definition engine.cpp:34
ReadData< bb::fr > get_lookup_accumulators(const MultiTableId id, const fr &key_a, const fr &key_b, const bool is_2_to_1_lookup)
Given a table ID and the key(s) for a key-value lookup, return the lookup accumulators.
@ HONK_DUMMY_MULTI
Definition types.hpp:132
BasicTable create_basic_table(const BasicTableId id, const size_t index)
const MultiTable & get_multitable(const MultiTableId id)
Return the multitable with the provided ID; construct all MultiTables if not constructed already.
Entry point for Barretenberg command-line interface.
typename Flavor::FF FF
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
Serialized state of a circuit.
std::vector< std::vector< std::vector< FF > > > selectors
std::vector< uint32_t > real_variable_index
std::unordered_map< uint32_t, uint64_t > range_tags
std::unordered_map< uint32_t, std::string > vars_of_interest
std::vector< std::vector< uint32_t > > ram_states
std::vector< std::vector< std::array< uint32_t, 2 > > > rom_states
std::vector< std::vector< std::vector< uint32_t > > > ram_records
std::vector< std::vector< std::vector< uint32_t > > > rom_records
std::vector< std::vector< std::vector< FF > > > lookup_tables
std::vector< uint32_t > real_variable_tags
std::vector< uint32_t > public_inps
std::vector< std::vector< std::vector< uint32_t > > > wires
Used to store instructions to create partial_non_native_field_multiplication gates.
static constexpr std::array< std::array< FF, t >, rounds_f+rounds_p > round_constants
static constexpr field one()
static constexpr uint256_t modulus
BB_INLINE constexpr field to_montgomery_form() const noexcept
A basic table from which we can perform lookups (for example, an xor table)
Definition types.hpp:287