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: Complete, auditors: [Luke, Raju], commit: }
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
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
76{
77 BB_BENCH_NAME("populate_public_inputs_block");
78
79 // Update the public inputs block
80 for (const auto& idx : this->public_inputs()) {
81 // first two wires get a copy of the public inputs
82 blocks.pub_inputs.populate_wires(idx, idx, this->zero_idx(), this->zero_idx());
83 for (auto& selector : this->blocks.pub_inputs.get_selectors()) {
84 selector.emplace_back(0);
85 }
86 }
87}
88
94// TODO(#423): This function adds valid (but arbitrary) gates to ensure that the circuit which includes
95// them will not result in any zero-polynomials. It also ensures that the first coefficient of the wire
96// polynomials is zero, which is required for them to be shiftable.
97template <typename ExecutionTrace>
99{
100 // q_m, q_1, q_2, q_3, q_4
101 blocks.arithmetic.populate_wires(this->zero_idx(), this->zero_idx(), this->zero_idx(), this->zero_idx());
102 blocks.arithmetic.q_m().emplace_back(1);
103 blocks.arithmetic.q_1().emplace_back(1);
104 blocks.arithmetic.q_2().emplace_back(1);
105 blocks.arithmetic.q_3().emplace_back(1);
106 blocks.arithmetic.q_4().emplace_back(1);
107 blocks.arithmetic.q_c().emplace_back(0);
108 blocks.arithmetic.set_gate_selector(0);
109 check_selector_length_consistency();
110 this->increment_num_gates();
111
112 // q_delta_range
113 blocks.delta_range.populate_wires(this->zero_idx(), this->zero_idx(), this->zero_idx(), this->zero_idx());
114 blocks.delta_range.q_m().emplace_back(0);
115 blocks.delta_range.q_1().emplace_back(0);
116 blocks.delta_range.q_2().emplace_back(0);
117 blocks.delta_range.q_3().emplace_back(0);
118 blocks.delta_range.q_4().emplace_back(0);
119 blocks.delta_range.q_c().emplace_back(0);
120 blocks.delta_range.set_gate_selector(1);
121
122 check_selector_length_consistency();
123 this->increment_num_gates();
124 create_unconstrained_gate(
125 blocks.delta_range, this->zero_idx(), this->zero_idx(), this->zero_idx(), this->zero_idx());
126
127 // q_elliptic
128 blocks.elliptic.populate_wires(this->zero_idx(), this->zero_idx(), this->zero_idx(), this->zero_idx());
129 blocks.elliptic.q_m().emplace_back(0);
130 blocks.elliptic.q_1().emplace_back(0);
131 blocks.elliptic.q_2().emplace_back(0);
132 blocks.elliptic.q_3().emplace_back(0);
133 blocks.elliptic.q_4().emplace_back(0);
134 blocks.elliptic.q_c().emplace_back(0);
135 blocks.elliptic.set_gate_selector(1);
136 check_selector_length_consistency();
137 this->increment_num_gates();
138 create_unconstrained_gate(blocks.elliptic, this->zero_idx(), this->zero_idx(), this->zero_idx(), this->zero_idx());
139
140 // q_memory
141 blocks.memory.populate_wires(this->zero_idx(), this->zero_idx(), this->zero_idx(), this->zero_idx());
142 blocks.memory.q_m().emplace_back(0);
143 blocks.memory.q_1().emplace_back(0);
144 blocks.memory.q_2().emplace_back(0);
145 blocks.memory.q_3().emplace_back(0);
146 blocks.memory.q_4().emplace_back(0);
147 blocks.memory.q_c().emplace_back(0);
148 blocks.memory.set_gate_selector(1);
149 check_selector_length_consistency();
150 this->increment_num_gates();
151 create_unconstrained_gate(blocks.memory, this->zero_idx(), this->zero_idx(), this->zero_idx(), this->zero_idx());
152
153 // q_nnf
154 blocks.nnf.populate_wires(this->zero_idx(), this->zero_idx(), this->zero_idx(), this->zero_idx());
155 blocks.nnf.q_m().emplace_back(0);
156 blocks.nnf.q_1().emplace_back(0);
157 blocks.nnf.q_2().emplace_back(0);
158 blocks.nnf.q_3().emplace_back(0);
159 blocks.nnf.q_4().emplace_back(0);
160 blocks.nnf.q_c().emplace_back(0);
161 blocks.nnf.set_gate_selector(1);
162 check_selector_length_consistency();
163 this->increment_num_gates();
164 create_unconstrained_gate(blocks.nnf, this->zero_idx(), this->zero_idx(), this->zero_idx(), this->zero_idx());
165
166 // Add nonzero values in w_4 and q_c (q_4*w_4 + q_c --> 1*1 - 1 = 0)
167 uint32_t one_idx = put_constant_variable(FF::one());
168 create_big_add_gate({ this->zero_idx(), this->zero_idx(), this->zero_idx(), one_idx, 0, 0, 0, 1, -1 });
169
170 // Take care of all polys related to lookups (q_lookup, tables, sorted, etc)
171 // by doing a dummy lookup with a special table.
172 // Note: the 4th table poly is the table index: this is not the value of the table
173 // type enum but rather the index of the table in the list of all tables utilized
174 // in the circuit. Therefore we naively need two different basic tables (indices 0, 1)
175 // to get a non-zero value in table_4.
176 // The multitable operates on 2-bit values, so the maximum is 3
177 uint32_t left_value = 3;
178 uint32_t right_value = 3;
179
180 FF left_witness_value = fr{ left_value, 0, 0, 0 }.to_montgomery_form();
181 FF right_witness_value = fr{ right_value, 0, 0, 0 }.to_montgomery_form();
182
183 uint32_t left_witness_index = this->add_variable(left_witness_value);
184 uint32_t right_witness_index = this->add_variable(right_witness_value);
185 const auto dummy_accumulators = plookup::get_lookup_accumulators(
186 plookup::MultiTableId::HONK_DUMMY_MULTI, left_witness_value, right_witness_value, true);
187 auto read_data = create_gates_from_plookup_accumulators(
188 plookup::MultiTableId::HONK_DUMMY_MULTI, dummy_accumulators, left_witness_index, right_witness_index);
189
190 update_used_witnesses(left_witness_index);
191 update_used_witnesses(right_witness_index);
192 std::array<std::vector<uint32_t>, 3> parse_read_data{ read_data[plookup::ColumnIdx::C1],
193 read_data[plookup::ColumnIdx::C2],
194 read_data[plookup::ColumnIdx::C3] };
195 for (const auto& column : parse_read_data) {
196 update_used_witnesses(column);
197 update_finalize_witnesses(column);
198 }
199
200 // mock a poseidon external gate, with all zeros as input
201 blocks.poseidon2_external.populate_wires(this->zero_idx(), this->zero_idx(), this->zero_idx(), this->zero_idx());
202 blocks.poseidon2_external.q_m().emplace_back(0);
203 blocks.poseidon2_external.q_1().emplace_back(0);
204 blocks.poseidon2_external.q_2().emplace_back(0);
205 blocks.poseidon2_external.q_3().emplace_back(0);
206 blocks.poseidon2_external.q_c().emplace_back(0);
207 blocks.poseidon2_external.q_4().emplace_back(0);
208 blocks.poseidon2_external.set_gate_selector(1);
209 check_selector_length_consistency();
210 this->increment_num_gates();
211
212 // unconstrained gate to be read into by previous poseidon external gate via shifts
213 create_unconstrained_gate(
214 blocks.poseidon2_external, this->zero_idx(), this->zero_idx(), this->zero_idx(), this->zero_idx());
215
216 // mock a poseidon internal gate, with all zeros as input
217 blocks.poseidon2_internal.populate_wires(this->zero_idx(), this->zero_idx(), this->zero_idx(), this->zero_idx());
218 blocks.poseidon2_internal.q_m().emplace_back(0);
219 blocks.poseidon2_internal.q_1().emplace_back(0);
220 blocks.poseidon2_internal.q_2().emplace_back(0);
221 blocks.poseidon2_internal.q_3().emplace_back(0);
222 blocks.poseidon2_internal.q_c().emplace_back(0);
223 blocks.poseidon2_internal.q_4().emplace_back(0);
224 blocks.poseidon2_internal.set_gate_selector(1);
225 check_selector_length_consistency();
226 this->increment_num_gates();
227
228 // dummy gate to be read into by previous poseidon internal gate via shifts
229 create_unconstrained_gate(
230 blocks.poseidon2_internal, this->zero_idx(), this->zero_idx(), this->zero_idx(), this->zero_idx());
231}
232
241template <typename ExecutionTrace> void UltraCircuitBuilder_<ExecutionTrace>::create_add_gate(const add_triple_<FF>& in)
242{
243 // Delegate to create_big_add_gate with 4th wire set to zero
244 create_big_add_gate({ .a = in.a,
245 .b = in.b,
246 .c = in.c,
247 .d = this->zero_idx(),
248 .a_scaling = in.a_scaling,
249 .b_scaling = in.b_scaling,
250 .c_scaling = in.c_scaling,
251 .d_scaling = 0,
252 .const_scaling = in.const_scaling });
253}
254
263template <typename ExecutionTrace>
265 const bool include_next_gate_w_4)
266{
267 this->assert_valid_variables({ in.a, in.b, in.c, in.d });
268 blocks.arithmetic.populate_wires(in.a, in.b, in.c, in.d);
269 // If include_next_gate_w_4 is true then we set q_arith = 2. In this case, the linear term in the ArithmeticRelation
270 // is scaled by a factor of 2. We compensate here by scaling the quadratic term by 2 to achieve the constraint:
271 // 2 * [q_m * w_1 * w_2 + \sum_{i=1..4} q_i * w_i + q_c + w_4_shift] = 0
272 const FF mul_scaling = include_next_gate_w_4 ? in.mul_scaling * FF(2) : in.mul_scaling;
273 blocks.arithmetic.q_m().emplace_back(mul_scaling);
274 blocks.arithmetic.q_1().emplace_back(in.a_scaling);
275 blocks.arithmetic.q_2().emplace_back(in.b_scaling);
276 blocks.arithmetic.q_3().emplace_back(in.c_scaling);
277 blocks.arithmetic.q_c().emplace_back(in.const_scaling);
278 blocks.arithmetic.q_4().emplace_back(in.d_scaling);
279 blocks.arithmetic.set_gate_selector(include_next_gate_w_4 ? 2 : 1);
280 check_selector_length_consistency();
281 this->increment_num_gates();
282}
283
292template <typename ExecutionTrace>
294 const bool include_next_gate_w_4)
296 this->assert_valid_variables({ in.a, in.b, in.c, in.d });
297 blocks.arithmetic.populate_wires(in.a, in.b, in.c, in.d);
298 blocks.arithmetic.q_m().emplace_back(0);
299 blocks.arithmetic.q_1().emplace_back(in.a_scaling);
300 blocks.arithmetic.q_2().emplace_back(in.b_scaling);
301 blocks.arithmetic.q_3().emplace_back(in.c_scaling);
302 blocks.arithmetic.q_c().emplace_back(in.const_scaling);
303 blocks.arithmetic.q_4().emplace_back(in.d_scaling);
304 blocks.arithmetic.set_gate_selector(include_next_gate_w_4 ? 2 : 1);
305 check_selector_length_consistency();
306 this->increment_num_gates();
307}
314template <typename ExecutionTrace>
316{
317 this->assert_valid_variables({ variable_index });
318
319 blocks.arithmetic.populate_wires(variable_index, variable_index, this->zero_idx(), this->zero_idx());
320 blocks.arithmetic.q_m().emplace_back(1);
321 blocks.arithmetic.q_1().emplace_back(-1);
322 blocks.arithmetic.q_2().emplace_back(0);
323 blocks.arithmetic.q_3().emplace_back(0);
324 blocks.arithmetic.q_c().emplace_back(0);
325 blocks.arithmetic.q_4().emplace_back(0);
326 blocks.arithmetic.set_gate_selector(1);
327 check_selector_length_consistency();
328 this->increment_num_gates();
330
337template <typename ExecutionTrace>
339{
340 this->assert_valid_variables({ in.a, in.b, in.c });
341
342 blocks.arithmetic.populate_wires(in.a, in.b, in.c, this->zero_idx());
343 blocks.arithmetic.q_m().emplace_back(in.q_m);
344 blocks.arithmetic.q_1().emplace_back(in.q_l);
345 blocks.arithmetic.q_2().emplace_back(in.q_r);
346 blocks.arithmetic.q_3().emplace_back(in.q_o);
347 blocks.arithmetic.q_c().emplace_back(in.q_c);
348 blocks.arithmetic.q_4().emplace_back(0);
349 blocks.arithmetic.set_gate_selector(1);
350 check_selector_length_consistency();
351 this->increment_num_gates();
352}
353
370template <typename ExecutionTrace>
372{
373 this->assert_valid_variables({ in.x1, in.x2, in.x3, in.y1, in.y2, in.y3 });
374
375 auto& block = blocks.elliptic;
376
377 // Determine whether we can fuse this addition operation into the previous gate in the block
378 bool can_fuse_into_previous_gate =
379 block.size() > 0 && /* a previous gate exists in the block */
380 block.w_r()[block.size() - 1] == in.x1 && /* output x coord of previous gate is input of this one */
381 block.w_o()[block.size() - 1] == in.y1; /* output y coord of previous gate is input of this one */
382
383 if (can_fuse_into_previous_gate) {
384 block.q_1().set(block.size() - 1, in.sign_coefficient); // set q_sign of previous gate
385 block.q_elliptic().set(block.size() - 1, 1); // set q_ecc of previous gate to 1
386 } else {
387 block.populate_wires(this->zero_idx(), in.x1, in.y1, this->zero_idx());
388 block.q_3().emplace_back(0);
389 block.q_4().emplace_back(0);
390 block.q_1().emplace_back(in.sign_coefficient);
391
392 block.q_2().emplace_back(0);
393 block.q_m().emplace_back(0);
394 block.q_c().emplace_back(0);
395 block.set_gate_selector(1);
396 check_selector_length_consistency();
397 this->increment_num_gates();
398 }
399 // Create the unconstrained gate with the output of the doubling to be read into by the previous gate via shifts
400 create_unconstrained_gate(block, in.x2, in.x3, in.y3, in.y2);
401}
402
419template <typename ExecutionTrace>
421{
422 this->assert_valid_variables({ in.x1, in.x3, in.y1, in.y3 });
423
424 auto& block = blocks.elliptic;
425
426 // Determine whether we can fuse this doubling operation into the previous gate in the block
427 bool can_fuse_into_previous_gate =
428 block.size() > 0 && /* a previous gate exists in the block */
429 block.w_r()[block.size() - 1] == in.x1 && /* output x coord of previous gate is input of this one */
430 block.w_o()[block.size() - 1] == in.y1; /* output y coord of previous gate is input of this one */
431
432 // If possible, update the previous gate to be the first gate in the pair, otherwise create a new gate
433 if (can_fuse_into_previous_gate) {
434 block.q_elliptic().set(block.size() - 1, 1); // set q_ecc of previous gate to 1
435 block.q_m().set(block.size() - 1, 1); // set q_m (q_is_double) of previous gate to 1
436 } else {
437 block.populate_wires(this->zero_idx(), in.x1, in.y1, this->zero_idx());
438 block.q_m().emplace_back(1);
439 block.q_1().emplace_back(0);
440 block.q_2().emplace_back(0);
441 block.q_3().emplace_back(0);
442 block.q_c().emplace_back(0);
443 block.q_4().emplace_back(0);
444 block.set_gate_selector(1);
445 check_selector_length_consistency();
446 this->increment_num_gates();
447 }
448 // Create the unconstrained gate with the output of the doubling to be read into by the previous gate via shifts
449 create_unconstrained_gate(block, this->zero_idx(), in.x3, in.y3, this->zero_idx());
451
458template <typename ExecutionTrace>
459void UltraCircuitBuilder_<ExecutionTrace>::fix_witness(const uint32_t witness_index, const FF& witness_value)
460{
461 this->assert_valid_variables({ witness_index });
462
463 // Mark as intentionally single-gate for boomerang detection
464 update_used_witnesses(witness_index);
465
466 blocks.arithmetic.populate_wires(witness_index, this->zero_idx(), this->zero_idx(), this->zero_idx());
467 blocks.arithmetic.q_m().emplace_back(0);
468 blocks.arithmetic.q_1().emplace_back(1);
469 blocks.arithmetic.q_2().emplace_back(0);
470 blocks.arithmetic.q_3().emplace_back(0);
471 blocks.arithmetic.q_c().emplace_back(-witness_value);
472 blocks.arithmetic.q_4().emplace_back(0);
473 blocks.arithmetic.set_gate_selector(1);
474 check_selector_length_consistency();
475 this->increment_num_gates();
476}
477
478template <typename ExecutionTrace>
480{
481 if (constant_variable_indices.contains(variable)) {
482 return constant_variable_indices.at(variable);
483 } else {
484 uint32_t variable_index = this->add_variable(variable);
485 fix_witness(variable_index, variable);
486 constant_variable_indices.insert({ variable, variable_index });
487 return variable_index;
488 }
489}
490
499template <typename ExecutionTrace>
501{
502 for (plookup::BasicTable& table : lookup_tables) {
503 if (table.id == id) {
504 return table;
505 }
506 }
507 // Table doesn't exist! So try to create it.
508 lookup_tables.emplace_back(plookup::create_basic_table(id, lookup_tables.size()));
509 return lookup_tables.back();
510}
511
539template <typename ExecutionTrace>
541 const plookup::MultiTableId& id,
542 const plookup::ReadData<FF>& read_values,
543 const uint32_t key_a_index,
544 std::optional<uint32_t> key_b_index)
545{
546 using plookup::ColumnIdx;
547
548 const auto& multi_table = plookup::get_multitable(id);
549 const size_t num_lookups = read_values[ColumnIdx::C1].size();
551
552 for (size_t i = 0; i < num_lookups; ++i) {
553 const bool is_first_lookup = (i == 0);
554 const bool is_last_lookup = (i == num_lookups - 1);
555
556 // Get basic lookup table; construct and add to builder.lookup_tables if not already present
557 plookup::BasicTable& table = get_table(multi_table.basic_table_ids[i]);
558 table.lookup_gates.emplace_back(read_values.lookup_entries[i]);
560 // Create witness variables: first lookup reuses user's input indices, subsequent create new variables
561 const auto first_idx = is_first_lookup ? key_a_index : this->add_variable(read_values[ColumnIdx::C1][i]);
562 const auto second_idx = (is_first_lookup && key_b_index.has_value())
563 ? *key_b_index
564 : this->add_variable(read_values[ColumnIdx::C2][i]);
565 const auto third_idx = this->add_variable(read_values[ColumnIdx::C3][i]);
566
567 read_data[ColumnIdx::C1].push_back(first_idx);
568 read_data[ColumnIdx::C2].push_back(second_idx);
569 read_data[ColumnIdx::C3].push_back(third_idx);
570 this->assert_valid_variables({ first_idx, second_idx, third_idx });
571
572 // Populate lookup gate: wire values and selectors
573 blocks.lookup.populate_wires(first_idx, second_idx, third_idx, this->zero_idx());
574 blocks.lookup.set_gate_selector(1); // mark as lookup gate
575 blocks.lookup.q_3().emplace_back(FF(table.table_index)); // unique table identifier
576 // Step size coefficients: zero for last lookup (no next accumulator), negative step sizes otherwise
577 blocks.lookup.q_2().emplace_back(is_last_lookup ? 0 : -multi_table.column_1_step_sizes[i + 1]);
578 blocks.lookup.q_m().emplace_back(is_last_lookup ? 0 : -multi_table.column_2_step_sizes[i + 1]);
579 blocks.lookup.q_c().emplace_back(is_last_lookup ? 0 : -multi_table.column_3_step_sizes[i + 1]);
580 blocks.lookup.q_1().emplace_back(0); // unused
581 blocks.lookup.q_4().emplace_back(0); // unused
582
583 check_selector_length_consistency();
584 this->increment_num_gates();
585 }
586 return read_data;
587}
588
592template <typename ExecutionTrace>
594 const uint64_t target_range)
596 RangeList result;
597 const auto range_tag = get_new_tag();
598 const auto tau_tag = get_new_tag();
599 set_tau_transposition(range_tag, tau_tag);
600 result.target_range = target_range;
601 result.range_tag = range_tag;
602 result.tau_tag = tau_tag;
603
604 uint64_t num_multiples_of_three = (target_range / DEFAULT_PLOOKUP_RANGE_STEP_SIZE);
605 // allocate the minimum number of variable indices required for the range constraint. this function is only called
606 // when we are creating a range constraint on a witness index, which is responsible for the extra + 1. (note that
607 // the below loop goes from 0 to `num_multiples_of_three` inclusive.)
608 result.variable_indices.reserve(static_cast<uint32_t>(num_multiples_of_three + 3));
609 for (uint64_t i = 0; i <= num_multiples_of_three; ++i) {
610 const uint32_t index = this->add_variable(fr(i * DEFAULT_PLOOKUP_RANGE_STEP_SIZE));
611 result.variable_indices.emplace_back(index);
612 assign_tag(index, result.range_tag);
613 }
614 // `target_range` may not be divisible by 3, so we explicitly add it also.
615 {
616 const uint32_t index = this->add_variable(fr(target_range));
617 result.variable_indices.emplace_back(index);
618 assign_tag(index, result.range_tag);
619 }
620 // Need this because these variables will not appear in the witness otherwise
621 create_unconstrained_gates(result.variable_indices);
622
623 return result;
624}
625
626template <typename ExecutionTrace>
628 const uint32_t variable_index, const uint64_t num_bits, const uint64_t target_range_bitnum, std::string const& msg)
629{
630 this->assert_valid_variables({ variable_index });
631 // make sure `num_bits` satisfies the correct bounds
632 BB_ASSERT_GT(num_bits, 0U);
633 BB_ASSERT_GTE(MAX_NUM_BITS_RANGE_CONSTRAINT, num_bits);
634
635 uint256_t val = (uint256_t)(this->get_variable(variable_index));
636
637 // If the value is out of range, set the CircuitBuilder error to the given msg.
638 if (val.get_msb() >= num_bits && !this->failed()) {
639 this->failure(msg);
640 }
641
642 // compute limb structure
643 const uint64_t sublimb_mask = (1ULL << target_range_bitnum) - 1;
644
645 std::vector<uint64_t> sublimbs;
646 std::vector<uint32_t> sublimb_indices;
647
648 const bool has_remainder_bits = (num_bits % target_range_bitnum != 0);
649 const uint64_t num_limbs = (num_bits / target_range_bitnum) + has_remainder_bits;
650 const uint64_t last_limb_size = num_bits - ((num_bits / target_range_bitnum) * target_range_bitnum);
651 const uint64_t last_limb_range = ((uint64_t)1 << last_limb_size) - 1;
652
653 // extract limbs from the value
654 uint256_t accumulator = val;
655 for (size_t i = 0; i < num_limbs; ++i) {
656 sublimbs.push_back(accumulator.data[0] & sublimb_mask);
657 accumulator = accumulator >> target_range_bitnum;
658 }
659 // set the correct range constraint on each limb. note that when there are remainder bits, the last limb must be
660 // constrained to a smaller range.
661 const size_t num_full_limbs = has_remainder_bits ? sublimbs.size() - 1 : sublimbs.size();
662 for (size_t i = 0; i < num_full_limbs; ++i) {
663 const auto limb_idx = this->add_variable(bb::fr(sublimbs[i]));
664 sublimb_indices.emplace_back(limb_idx);
665 create_small_range_constraint(limb_idx, sublimb_mask);
666 }
667 if (has_remainder_bits) {
668 const auto limb_idx = this->add_variable(bb::fr(sublimbs.back()));
669 sublimb_indices.emplace_back(limb_idx);
670 create_small_range_constraint(limb_idx, last_limb_range);
671 }
672
673 // Prove that the limbs reconstruct the original value by processing limbs in groups of 3.
674 // We constrain: value = sum_{j=0}^{num_limbs-1} limb[j] * 2^(j * target_range_bitnum)
675 //
676 // Each iteration subtracts 3 limbs' contributions from an accumulator (starting at `val`),
677 // and constrains that the accumulator updates correctly via an arithmetic gate.
678 const uint64_t num_limb_triples = (num_limbs / 3) + ((num_limbs % 3) != 0);
679 // `leftovers` is the number of real limbs in the final triple (1, 2, or 3).
680 const uint64_t leftovers = (num_limbs % 3) == 0 ? 3 : (num_limbs % 3);
682 accumulator = val;
683 uint32_t accumulator_idx = variable_index;
684 // loop goes from `i = 0` to `num_limb_triples`, but some special case must be taken for the last triple (`i ==
685 // num_limb_triples - 1`), hence some conditional logic.
686 for (size_t i = 0; i < num_limb_triples; ++i) {
687 // `real_limbs` which limb positions in this triple contain actual limbs vs zero-padding.
688 // When `i == num_limb_triples - 1`, some positions may be unused if `num_limbs` isn't divisible by 3.
689 const bool real_limbs[3]{
690 !(i == (num_limb_triples - 1) && (leftovers < 1)),
691 !(i == (num_limb_triples - 1) && (leftovers < 2)),
692 !(i == (num_limb_triples - 1) && (leftovers < 3)),
693 };
694
695 // The witness values of the 3 limbs in this triple (0 for padding positions).
696 const uint64_t round_sublimbs[3]{
697 real_limbs[0] ? sublimbs[3 * i] : 0,
698 real_limbs[1] ? sublimbs[3 * i + 1] : 0,
699 real_limbs[2] ? sublimbs[3 * i + 2] : 0,
700 };
701 // The witnesss indices of the current 3 limbs (zero_idx for padding positions).
702 const uint32_t new_limbs[3]{
703 real_limbs[0] ? sublimb_indices[3 * i] : this->zero_idx(),
704 real_limbs[1] ? sublimb_indices[3 * i + 1] : this->zero_idx(),
705 real_limbs[2] ? sublimb_indices[3 * i + 2] : this->zero_idx(),
706 };
707 // Bit-shifts for each limb: limb[3*i+k] contributes at bit position (3*i+k) * target_range_bitnum.
708 const uint64_t shifts[3]{
709 target_range_bitnum * (3 * i),
710 target_range_bitnum * (3 * i + 1),
711 target_range_bitnum * (3 * i + 2),
712 };
713 // Compute the new accumulator after subtracting this triple's contribution.
714 // After the final iteration, accumulator should be 0.
715 uint256_t new_accumulator = accumulator - (uint256_t(round_sublimbs[0]) << shifts[0]) -
716 (uint256_t(round_sublimbs[1]) << shifts[1]) -
717 (uint256_t(round_sublimbs[2]) << shifts[2]);
718
719 // This `big_add_gate` has differing behavior depending on whether or not `i == num_limb_triples - 1`.
720 // If `i != num_limb_triples - 1`, then the constraint will be limb[0]*2^shift[0] + limb[1]*2^shift[1] +
721 // limb[2]*2^shift[2] - acc = new_accumulator (the last argument to `create_big_add_gate` is `true`, means the
722 // sum is w_4-shift, which will be the witness corresponding to what is currently `new_accumulator`.).
723 // If `i == num_limb_triples - 1`, then the last argument to `create_big_add_gate` is false, so the constraint
724 // is limb[0]*2^shift[0] + limb[1]*2^shift[1] + limb[2]*2^shift[2] - acc = 0.
725 //
726 // N.B. When `num_bits` is small, we only have remainder bits. This last constraint, checking the correctness of
727 // the limb-decomposition, ensures that the variable is not orphaned. (See the warning in
728 // `create_small_range_constraint`.)
729 create_big_add_gate(
730 {
731 new_limbs[0],
732 new_limbs[1],
733 new_limbs[2],
734 accumulator_idx,
735 uint256_t(1) << shifts[0],
736 uint256_t(1) << shifts[1],
737 uint256_t(1) << shifts[2],
738 -1,
739 0,
740 },
741 (i != num_limb_triples - 1));
742 if (i != num_limb_triples - 1) {
743 accumulator_idx = this->add_variable(fr(new_accumulator));
744 accumulator = new_accumulator;
745 }
746 }
747 return sublimb_indices;
748}
749
750template <typename ExecutionTrace>
752 const uint64_t target_range,
753 std::string const msg)
754{
755 // make sure `target_range` is not too big.
756 BB_ASSERT_GTE(MAX_SMALL_RANGE_CONSTRAINT_VAL, target_range);
757 const bool is_out_of_range = (uint256_t(this->get_variable(variable_index)).data[0] > target_range);
758 if (is_out_of_range && !this->failed()) {
759 this->failure(msg);
760 }
761 if (range_lists.count(target_range) == 0) {
762 range_lists.insert({ target_range, create_range_list(target_range) });
763 }
764 // The tag of `variable_index` is `DEFAULT_TAG` if it has never been range-constrained and a non-trivial value
765 // otherwise.
766 const auto existing_tag = this->real_variable_tags[this->real_variable_index[variable_index]];
767 auto& list = range_lists[target_range];
768
769 // If the variable's tag matches the target range list's tag, do nothing; the variable has _already_ been
770 // constrained to this exact range (i.e., `create_new_range_constraint(variable_index, target_range)` has already
771 // been called).
772 if (existing_tag == list.range_tag) {
773 return;
774 }
775 // If the variable is 'untagged' (i.e., it has the dummy tag), assign it the appropriate tag, which amounts to
776 // setting the range-constraint.
777 if (existing_tag == DEFAULT_TAG) {
778 assign_tag(variable_index, list.range_tag);
779 list.variable_indices.emplace_back(variable_index);
780 return;
781 }
782 // Otherwise, find the range for which the variable has already been tagged.
783 bool found_tag = false;
784 for (const auto& r : range_lists) {
785 if (r.second.range_tag == existing_tag) {
786 found_tag = true;
787 if (r.first < target_range) {
788 // The variable already has a more restrictive range check, so do nothing.
789 return;
790 }
791 // The range constraint we are trying to impose is more restrictive than the existing range
792 // constraint. It would be difficult to remove an existing range check. Instead, arithmetically copy the
793 // variable and apply a range check to new variable. We do _not_ simply create a
794 // copy-constraint, because that would copy the tag, which exactly corresponds to the old (less
795 // restrictive) range constraint. Instead, we use an arithmetic gate to constrain the value of
796 // the new variable and set the tag (a.k.a. range-constraint) via a new call to
797 // `create_new_range_constraint`.
798 const uint32_t copied_witness = this->add_variable(this->get_variable(variable_index));
799 create_add_gate({ .a = variable_index,
800 .b = copied_witness,
801 .c = this->zero_idx(),
802 .a_scaling = 1,
803 .b_scaling = -1,
804 .c_scaling = 0,
805 .const_scaling = 0 });
806 // Recurse with new witness that has no tag attached.
807 create_small_range_constraint(copied_witness, target_range, msg);
808 return;
809 }
810 }
811 // should never occur
812 BB_ASSERT(found_tag);
813}
814
815template <typename ExecutionTrace> void UltraCircuitBuilder_<ExecutionTrace>::process_range_list(RangeList& list)
816{
817 this->assert_valid_variables(list.variable_indices);
818
819 BB_ASSERT_GT(list.variable_indices.size(), 0U);
820
821 // replace witness-index in variable_indices with the corresponding real-variable-index i.e., if a copy constraint
822 // has been applied on a variable after it was range constrained, this makes sure the indices in list point to the
823 // updated index in the range list so the set equivalence does not fail
824 for (uint32_t& x : list.variable_indices) {
825 x = this->real_variable_index[x];
826 }
827 // Sort `variable_indices` and remove duplicate witness indices to prevent the sorted list set size being wrong!
828 std::sort(list.variable_indices.begin(), list.variable_indices.end());
829 auto back_iterator = std::unique(list.variable_indices.begin(), list.variable_indices.end());
830 list.variable_indices.erase(back_iterator, list.variable_indices.end());
831
832 // Extract the values of each (real) variable into a list to be sorted (in the sense of the range/plookup-style
833 // argument).
834 std::vector<uint32_t> sorted_list;
835 sorted_list.reserve(list.variable_indices.size());
836 for (const auto variable_index : list.variable_indices) {
837 // note that `field_element` is < 32 bits as the corresponding witness has a non-trivial range-constraint.
838 const auto& field_element = this->get_variable(variable_index);
839 const uint32_t shrinked_value = (uint32_t)field_element.from_montgomery_form().data[0];
840 sorted_list.emplace_back(shrinked_value);
841 }
842
843#ifdef NO_PAR_ALGOS
844 std::sort(sorted_list.begin(), sorted_list.end());
845#else
846 std::sort(std::execution::par_unseq, sorted_list.begin(), sorted_list.end());
847#endif
848 // list must be padded to a multipe of 4 and larger than 4 (gate_width)
849 constexpr size_t gate_width = NUM_WIRES;
850 size_t padding = (gate_width - (list.variable_indices.size() % gate_width)) % gate_width;
851
852 std::vector<uint32_t> indices;
853 indices.reserve(padding + sorted_list.size());
854
855 if (list.variable_indices.size() <= gate_width) {
856 padding += gate_width;
857 }
858 for (size_t i = 0; i < padding; ++i) {
859 indices.emplace_back(this->zero_idx());
860 }
861 // tag the elements in the sorted_list to apply the multiset-equality check implicit in range-constraints.
862 for (const auto sorted_value : sorted_list) {
863 const uint32_t index = this->add_variable(fr(sorted_value));
864 assign_tag(index, list.tau_tag);
865 indices.emplace_back(index);
866 }
867 // constrain the _sorted_ list: starts at 0, ends at `target_range`, consecutive differences in {0, 1, 2, 3}.
868 create_sort_constraint_with_edges(indices, 0, list.target_range);
869}
870
871template <typename ExecutionTrace> void UltraCircuitBuilder_<ExecutionTrace>::process_range_lists()
872{
873 for (auto& i : range_lists) {
874 process_range_list(i.second);
875 }
876}
877
878template <typename ExecutionTrace>
879void UltraCircuitBuilder_<ExecutionTrace>::enforce_small_deltas(const std::vector<uint32_t>& variable_indices)
880{
881 constexpr size_t gate_width = NUM_WIRES;
882 BB_ASSERT_EQ(variable_indices.size() % gate_width, 0U);
883 this->assert_valid_variables(variable_indices);
884
885 for (size_t i = 0; i < variable_indices.size(); i += gate_width) {
886 blocks.delta_range.populate_wires(
887 variable_indices[i], variable_indices[i + 1], variable_indices[i + 2], variable_indices[i + 3]);
888
889 this->increment_num_gates();
890 blocks.delta_range.q_m().emplace_back(0);
891 blocks.delta_range.q_1().emplace_back(0);
892 blocks.delta_range.q_2().emplace_back(0);
893 blocks.delta_range.q_3().emplace_back(0);
894 blocks.delta_range.q_c().emplace_back(0);
895 blocks.delta_range.q_4().emplace_back(0);
896 blocks.delta_range.set_gate_selector(1);
897 check_selector_length_consistency();
898 }
899 // dummy gate needed because of widget's check of next row
900 create_unconstrained_gate(blocks.delta_range,
901 variable_indices[variable_indices.size() - 1],
902 this->zero_idx(),
903 this->zero_idx(),
904 this->zero_idx());
905}
906
907// useful to put variables in the witness that aren't already used - e.g. the dummy variables of the range constraint in
908// multiples of four
909template <typename ExecutionTrace>
910void UltraCircuitBuilder_<ExecutionTrace>::create_unconstrained_gates(const std::vector<uint32_t>& variable_index)
911{
912 std::vector<uint32_t> padded_list = variable_index;
913 constexpr size_t gate_width = NUM_WIRES;
914 const uint64_t padding = (gate_width - (padded_list.size() % gate_width)) % gate_width;
915 for (uint64_t i = 0; i < padding; ++i) {
916 padded_list.emplace_back(this->zero_idx());
917 }
918 this->assert_valid_variables(variable_index);
919 this->assert_valid_variables(padded_list);
920
921 for (size_t i = 0; i < padded_list.size(); i += gate_width) {
922 create_unconstrained_gate(
923 blocks.arithmetic, padded_list[i], padded_list[i + 1], padded_list[i + 2], padded_list[i + 3]);
924 }
925}
926
927template <typename ExecutionTrace>
929 const std::vector<uint32_t>& variable_indices, 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_indices.size() % gate_width, 0U);
934 BB_ASSERT_GT(variable_indices.size(), gate_width);
935 this->assert_valid_variables(variable_indices);
936 // only work with the delta_range block. this forces: `w_2 - w_1`, `w_3 - w_2`, `w_4 - w_3`, and `w_1_shift - w_4`
937 // to be in {0, 1, 2, 3}.
938 auto& block = blocks.delta_range;
939
940 // Add an arithmetic gate to ensure the first input is equal to the start value of the range being checked
941 create_add_gate({ variable_indices[0], this->zero_idx(), this->zero_idx(), 1, 0, 0, -start });
942
943 // enforce delta range relation for all rows (there are `variabe_indices.size() / gate_width`). note that there are
944 // at least two rows.
945 for (size_t i = 0; i < variable_indices.size(); i += gate_width) {
946
947 block.populate_wires(
948 variable_indices[i], variable_indices[i + 1], variable_indices[i + 2], variable_indices[i + 3]);
949 this->increment_num_gates();
950 block.q_m().emplace_back(0);
951 block.q_1().emplace_back(0);
952 block.q_2().emplace_back(0);
953 block.q_3().emplace_back(0);
954 block.q_c().emplace_back(0);
955 block.q_4().emplace_back(0);
956 block.set_gate_selector(1);
957 check_selector_length_consistency();
958 }
959
960 // the delta_range constraint has to have access to w_1-shift (it checks that w_1-shift - w_4 is in {0, 1, 2, 3}).
961 // Therefore, we repeat the last element in an unconstrained gate.
962 create_unconstrained_gate(
963 block, variable_indices[variable_indices.size() - 1], this->zero_idx(), this->zero_idx(), this->zero_idx());
964 // arithmetic gate to constrain that `variable_indices[last] == end`, i.e., verify the boundary condition.
965 create_add_gate(
966 { variable_indices[variable_indices.size() - 1], this->zero_idx(), this->zero_idx(), 1, 0, 0, -end });
967}
968
991template <typename ExecutionTrace>
993{
994 auto& block = blocks.memory;
995 block.set_gate_selector(type == MEMORY_SELECTORS::MEM_NONE ? 0 : 1);
996 switch (type) {
997 case MEMORY_SELECTORS::ROM_CONSISTENCY_CHECK: {
998 // Memory read gate used with the sorted list of memory reads.
999 // Apply sorted memory read checks with the following additional check:
1000 // 1. Assert that if index field across two gates does not change, the value field does not change.
1001 // Used for ROM reads and RAM reads across write/read boundaries
1002 block.q_1().emplace_back(1);
1003 block.q_2().emplace_back(1);
1004 block.q_3().emplace_back(0);
1005 block.q_4().emplace_back(0);
1006 block.q_m().emplace_back(0);
1007 block.q_c().emplace_back(0);
1008 check_selector_length_consistency();
1009 break;
1010 }
1011 case MEMORY_SELECTORS::RAM_CONSISTENCY_CHECK: {
1012 // Memory read gate used with the sorted list of memory reads.
1013 // 1. Validate adjacent index values across 2 gates increases by 0 or 1
1014 // 2. Validate record computation (r = read_write_flag + index * \eta + \timestamp * \eta^2 + value * \eta^3)
1015 // 3. If adjacent index values across 2 gates does not change, and the next gate's read_write_flag is set to
1016 // 'read', validate adjacent values do not change Used for ROM reads and RAM reads across read/write boundaries
1017 block.q_1().emplace_back(0);
1018 block.q_2().emplace_back(0);
1019 block.q_3().emplace_back(1);
1020 block.q_4().emplace_back(0);
1021 block.q_m().emplace_back(0);
1022 block.q_c().emplace_back(0);
1023 check_selector_length_consistency();
1024 break;
1025 }
1026 case MEMORY_SELECTORS::RAM_TIMESTAMP_CHECK: {
1027 // For two adjacent RAM entries that share the same index, validate the timestamp value is monotonically
1028 // increasing
1029 block.q_1().emplace_back(1);
1030 block.q_2().emplace_back(0);
1031 block.q_3().emplace_back(0);
1032 block.q_4().emplace_back(1);
1033 block.q_m().emplace_back(0);
1034 block.q_c().emplace_back(0);
1035 check_selector_length_consistency();
1036 break;
1037 }
1038 case MEMORY_SELECTORS::ROM_READ: {
1039 // Memory read gate for reading memory cells. Also used for the _initialization_ of ROM memory cells.
1040 // Validates record witness computation (r = read_write_flag + index * \eta + timestamp * \eta^2 + value *
1041 // \eta^3)
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(0);
1046 block.q_m().emplace_back(1); // validate record witness is correctly computed
1047 block.q_c().emplace_back(0); // read/write flag stored in q_c
1048 check_selector_length_consistency();
1049 break;
1050 }
1051 case MEMORY_SELECTORS::RAM_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_WRITE: {
1065 // Memory read gate for writing 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(1); // read/write flag stored in q_c
1074 check_selector_length_consistency();
1075 break;
1076 }
1077 default: {
1078 block.q_1().emplace_back(0);
1079 block.q_2().emplace_back(0);
1080 block.q_3().emplace_back(0);
1081 block.q_4().emplace_back(0);
1082 block.q_m().emplace_back(0);
1083 block.q_c().emplace_back(0);
1084 check_selector_length_consistency();
1085 break;
1086 }
1087 }
1088}
1089
1113template <typename ExecutionTrace>
1115{
1116 auto& block = blocks.nnf;
1117 block.set_gate_selector(type == NNF_SELECTORS::NNF_NONE ? 0 : 1);
1118 switch (type) {
1119 case NNF_SELECTORS::LIMB_ACCUMULATE_1: {
1120 block.q_1().emplace_back(0);
1121 block.q_2().emplace_back(0);
1122 block.q_3().emplace_back(1);
1123 block.q_4().emplace_back(1);
1124 block.q_m().emplace_back(0);
1125 block.q_c().emplace_back(0);
1126 check_selector_length_consistency();
1127 break;
1128 }
1129 case NNF_SELECTORS::LIMB_ACCUMULATE_2: {
1130 block.q_1().emplace_back(0);
1131 block.q_2().emplace_back(0);
1132 block.q_3().emplace_back(1);
1133 block.q_4().emplace_back(0);
1134 block.q_m().emplace_back(1);
1135 block.q_c().emplace_back(0);
1136 check_selector_length_consistency();
1137 break;
1138 }
1139 case NNF_SELECTORS::NON_NATIVE_FIELD_1: {
1140 block.q_1().emplace_back(0);
1141 block.q_2().emplace_back(1);
1142 block.q_3().emplace_back(1);
1143 block.q_4().emplace_back(0);
1144 block.q_m().emplace_back(0);
1145 block.q_c().emplace_back(0);
1146 check_selector_length_consistency();
1147 break;
1148 }
1149 case NNF_SELECTORS::NON_NATIVE_FIELD_2: {
1150 block.q_1().emplace_back(0);
1151 block.q_2().emplace_back(1);
1152 block.q_3().emplace_back(0);
1153 block.q_4().emplace_back(1);
1154 block.q_m().emplace_back(0);
1155 block.q_c().emplace_back(0);
1156 check_selector_length_consistency();
1157 break;
1158 }
1159 case NNF_SELECTORS::NON_NATIVE_FIELD_3: {
1160 block.q_1().emplace_back(0);
1161 block.q_2().emplace_back(1);
1162 block.q_3().emplace_back(0);
1163 block.q_4().emplace_back(0);
1164 block.q_m().emplace_back(1);
1165 block.q_c().emplace_back(0);
1166 check_selector_length_consistency();
1167 break;
1168 }
1169 default: {
1170 block.q_1().emplace_back(0);
1171 block.q_2().emplace_back(0);
1172 block.q_3().emplace_back(0);
1173 block.q_4().emplace_back(0);
1174 block.q_m().emplace_back(0);
1175 block.q_c().emplace_back(0);
1176 check_selector_length_consistency();
1177 break;
1178 }
1179 }
1180}
1181
1192template <typename ExecutionTrace>
1194 const uint32_t hi_idx,
1195 const size_t lo_limb_bits,
1196 const size_t hi_limb_bits,
1197 std::string const& msg)
1198{
1199 // Validate limbs are <= 70 bits. If limbs are larger we require more witnesses and cannot use our limb accumulation
1200 // custom gate
1201 BB_ASSERT_LTE(lo_limb_bits, 14U * 5U);
1202 BB_ASSERT_LTE(hi_limb_bits, 14U * 5U);
1203
1204 // If the value is larger than the range, we log the error in builder
1205 const bool is_lo_out_of_range = (uint256_t(this->get_variable(lo_idx)) >= (uint256_t(1) << lo_limb_bits));
1206 if (is_lo_out_of_range && !this->failed()) {
1207 this->failure(msg + ": lo limb.");
1208 }
1209 const bool is_hi_out_of_range = (uint256_t(this->get_variable(hi_idx)) >= (uint256_t(1) << hi_limb_bits));
1210 if (is_hi_out_of_range && !this->failed()) {
1211 this->failure(msg + ": hi limb.");
1212 }
1213
1214 // Sometimes we try to use limbs that are too large. It's easier to catch this issue here
1215 const auto get_sublimbs = [&](const uint32_t& limb_idx, const std::array<uint64_t, 5>& sublimb_masks) {
1216 const uint256_t limb = this->get_variable(limb_idx);
1217 // we can use constant 2^14 - 1 mask here. If the sublimb value exceeds the expected value then witness will
1218 // fail the range check below
1219 // We also use zero_idx to substitute variables that should be zero
1220 constexpr uint256_t MAX_SUBLIMB_MASK = (uint256_t(1) << 14) - 1;
1221 std::array<uint32_t, 5> sublimb_indices;
1222 sublimb_indices[0] = sublimb_masks[0] != 0 ? this->add_variable(fr(limb & MAX_SUBLIMB_MASK)) : this->zero_idx();
1223 sublimb_indices[1] =
1224 sublimb_masks[1] != 0 ? this->add_variable(fr((limb >> 14) & MAX_SUBLIMB_MASK)) : this->zero_idx();
1225 sublimb_indices[2] =
1226 sublimb_masks[2] != 0 ? this->add_variable(fr((limb >> 28) & MAX_SUBLIMB_MASK)) : this->zero_idx();
1227 sublimb_indices[3] =
1228 sublimb_masks[3] != 0 ? this->add_variable(fr((limb >> 42) & MAX_SUBLIMB_MASK)) : this->zero_idx();
1229 sublimb_indices[4] =
1230 sublimb_masks[4] != 0 ? this->add_variable(fr((limb >> 56) & MAX_SUBLIMB_MASK)) : this->zero_idx();
1231 return sublimb_indices;
1232 };
1233
1234 const auto get_limb_masks = [](size_t limb_bits) {
1235 std::array<uint64_t, 5> sublimb_masks;
1236 sublimb_masks[0] = limb_bits >= 14 ? 14 : limb_bits;
1237 sublimb_masks[1] = limb_bits >= 28 ? 14 : (limb_bits > 14 ? limb_bits - 14 : 0);
1238 sublimb_masks[2] = limb_bits >= 42 ? 14 : (limb_bits > 28 ? limb_bits - 28 : 0);
1239 sublimb_masks[3] = limb_bits >= 56 ? 14 : (limb_bits > 42 ? limb_bits - 42 : 0);
1240 sublimb_masks[4] = (limb_bits > 56 ? limb_bits - 56 : 0);
1241
1242 for (auto& mask : sublimb_masks) {
1243 mask = (1ULL << mask) - 1ULL;
1244 }
1245 return sublimb_masks;
1246 };
1247
1248 const auto lo_masks = get_limb_masks(lo_limb_bits);
1249 const auto hi_masks = get_limb_masks(hi_limb_bits);
1250 const std::array<uint32_t, 5> lo_sublimbs = get_sublimbs(lo_idx, lo_masks);
1251 const std::array<uint32_t, 5> hi_sublimbs = get_sublimbs(hi_idx, hi_masks);
1252
1253 blocks.nnf.populate_wires(lo_sublimbs[0], lo_sublimbs[1], lo_sublimbs[2], lo_idx);
1254 blocks.nnf.populate_wires(lo_sublimbs[3], lo_sublimbs[4], hi_sublimbs[0], hi_sublimbs[1]);
1255 blocks.nnf.populate_wires(hi_sublimbs[2], hi_sublimbs[3], hi_sublimbs[4], hi_idx);
1256
1257 apply_nnf_selectors(NNF_SELECTORS::LIMB_ACCUMULATE_1);
1258 apply_nnf_selectors(NNF_SELECTORS::LIMB_ACCUMULATE_2);
1259 apply_nnf_selectors(NNF_SELECTORS::NNF_NONE);
1260 this->increment_num_gates(3);
1261
1262 for (size_t i = 0; i < 5; i++) {
1263 if (lo_masks[i] != 0) {
1264 create_small_range_constraint(
1265 lo_sublimbs[i], lo_masks[i], "ultra_circuit_builder: sublimb of low too large");
1266 }
1267 if (hi_masks[i] != 0) {
1268 create_small_range_constraint(
1269 hi_sublimbs[i], hi_masks[i], "ultra_circuit_builder: sublimb of hi too large");
1270 }
1271 }
1272};
1273
1289template <typename ExecutionTrace>
1292{
1293 const auto [a0, a1, a2, a3] = std::array{ this->get_variable(input.a[0]),
1294 this->get_variable(input.a[1]),
1295 this->get_variable(input.a[2]),
1296 this->get_variable(input.a[3]) };
1297 const auto [b0, b1, b2, b3] = std::array{ this->get_variable(input.b[0]),
1298 this->get_variable(input.b[1]),
1299 this->get_variable(input.b[2]),
1300 this->get_variable(input.b[3]) };
1301 const auto [q0, q1, q2, q3] = std::array{ this->get_variable(input.q[0]),
1302 this->get_variable(input.q[1]),
1303 this->get_variable(input.q[2]),
1304 this->get_variable(input.q[3]) };
1305 const auto [r0, r1, r2, r3] = std::array{ this->get_variable(input.r[0]),
1306 this->get_variable(input.r[1]),
1307 this->get_variable(input.r[2]),
1308 this->get_variable(input.r[3]) };
1309 const auto& p_neg = input.neg_modulus;
1310
1311 constexpr FF LIMB_SHIFT = uint256_t(1) << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS;
1312 constexpr FF LIMB_RSHIFT = FF(1) / FF(uint256_t(1) << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS);
1313 constexpr FF LIMB_RSHIFT_2 = FF(1) / FF(uint256_t(1) << (2 * DEFAULT_NON_NATIVE_FIELD_LIMB_BITS));
1314
1315 // lo_0 = (a0·b0 - r0) + (a1·b0 + a0·b1)·2^L
1316 FF lo_0 = (a0 * b0 - r0) + (a1 * b0 + a0 * b1) * LIMB_SHIFT;
1317 // lo_1 = (lo_0 + q0·p0' + (q1·p0' + q0·p1' - r1)·2^L) / 2^2L
1318 FF lo_1 = (lo_0 + q0 * p_neg[0] + (q1 * p_neg[0] + q0 * p_neg[1] - r1) * LIMB_SHIFT) * LIMB_RSHIFT_2;
1319
1320 // hi_0 = (a2·b0 + a0·b2) + (a0·b3 + a3·b0 - r3)·2^L
1321 FF hi_0 = (a2 * b0 + a0 * b2) + (a0 * b3 + a3 * b0 - r3) * LIMB_SHIFT;
1322 // hi_1 = hi_0 + (a1·b1 - r2) + (a1·b2 + a2·b1)·2^L
1323 FF hi_1 = hi_0 + (a1 * b1 - r2) + (a1 * b2 + a2 * b1) * LIMB_SHIFT;
1324 // hi_2 = hi_1 + lo_1 + q2·p0' + (q3·p0' + q2·p1')·2^L
1325 FF hi_2 = hi_1 + lo_1 + q2 * p_neg[0] + (q3 * p_neg[0] + q2 * p_neg[1]) * LIMB_SHIFT;
1326 // hi_3 = (hi_2 + q0·p2' + q1·p1' + (q0·p3' + q1·p2')·2^L) / 2^2L
1327 FF hi_3 = (hi_2 + q0 * p_neg[2] + q1 * p_neg[1] + (q0 * p_neg[3] + q1 * p_neg[2]) * LIMB_SHIFT) * LIMB_RSHIFT_2;
1328
1329 const uint32_t lo_0_idx = this->add_variable(lo_0);
1330 const uint32_t lo_1_idx = this->add_variable(lo_1);
1331 const uint32_t hi_0_idx = this->add_variable(hi_0);
1332 const uint32_t hi_1_idx = this->add_variable(hi_1);
1333 const uint32_t hi_2_idx = this->add_variable(hi_2);
1334 const uint32_t hi_3_idx = this->add_variable(hi_3);
1335
1336 // Gate 1: big_add_gate to validate lo_1
1337 // (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
1338 // This constraint requires two rows in the trace: an arithmetic gate plus an unconstrained arithmetic gate
1339 // containing lo_0 in wire 4 so that the previous gate can access it via shifts. (We cannot use the next nnf gate
1340 // for this purpose since our trace is sorted by gate type).
1341 create_big_add_gate({ input.q[0],
1342 input.q[1],
1343 input.r[1],
1344 lo_1_idx,
1345 input.neg_modulus[0] + input.neg_modulus[1] * LIMB_SHIFT,
1346 input.neg_modulus[0] * LIMB_SHIFT,
1347 -LIMB_SHIFT,
1348 -LIMB_SHIFT.sqr(),
1349 0 },
1350 /*include_next_gate_w_4*/ true);
1351 // Gate 2: unconstrained gate to provide lo_0 via w_4_shift for gate 1
1352 create_unconstrained_gate(blocks.arithmetic, this->zero_idx(), this->zero_idx(), this->zero_idx(), lo_0_idx);
1353
1354 //
1355 // a = (a3 || a2 || a1 || a0) = (a3 * 2^b + a2) * 2^b + (a1 * 2^b + a0)
1356 // b = (b3 || b2 || b1 || b0) = (b3 * 2^b + b2) * 2^b + (b1 * 2^b + b0)
1357 //
1358 // Gate 3: NNF gate to check if lo_0 was computed correctly
1359 // The gate structure for the nnf gates is as follows:
1360 //
1361 // | a1 | b1 | r0 | lo_0 | <-- Gate 3: check lo_0
1362 // | a0 | b0 | a3 | b3 |
1363 // | a2 | b2 | r3 | hi_0 |
1364 // | a1 | b1 | r2 | hi_1 |
1365 //
1366 // Constraint: lo_0 = (a1 * b0 + a0 * b1) * 2^b + (a0 * b0) - r0
1367 // w4 = (w1 * w'2 + w'1 * w2) * 2^b + (w'1 * w'2) - w3
1368 //
1369 blocks.nnf.populate_wires(input.a[1], input.b[1], input.r[0], lo_0_idx);
1370 apply_nnf_selectors(NNF_SELECTORS::NON_NATIVE_FIELD_1);
1371 this->increment_num_gates();
1372
1373 //
1374 // Gate 4: NNF gate to check if hi_0 was computed correctly
1375 //
1376 // | a1 | b1 | r0 | lo_0 |
1377 // | a0 | b0 | a3 | b3 | <-- Gate 4: check hi_0
1378 // | a2 | b2 | r3 | hi_0 |
1379 // | a1 | b1 | r2 | hi_1 |
1380 //
1381 // Constraint: hi_0 = (a0 * b3 + a3 * b0 - r3) * 2^b + (a0 * b2 + a2 * b0)
1382 // w'4 = (w1 * w4 + w2 * w3 - w'3) * 2^b + (w1 * w'2 + w'1 * w2)
1383 //
1384 blocks.nnf.populate_wires(input.a[0], input.b[0], input.a[3], input.b[3]);
1385 apply_nnf_selectors(NNF_SELECTORS::NON_NATIVE_FIELD_2);
1386 this->increment_num_gates();
1387
1388 //
1389 // Gate 5: NNF gate to check if hi_1 was computed correctly
1390 //
1391 // | a1 | b1 | r0 | lo_0 |
1392 // | a0 | b0 | a3 | b3 |
1393 // | a2 | b2 | r3 | hi_0 | <-- Gate 5: check hi_1
1394 // | a1 | b1 | r2 | hi_1 |
1395 //
1396 // Constraint: hi_1 = hi_0 + (a2 * b1 + a1 * b2) * 2^b + (a1 * b1) - r2
1397 // w'4 = w4 + (w1 * w'2 + w'1 * w2) * 2^b + (w'1 * w'2) - w'3
1398 //
1399 blocks.nnf.populate_wires(input.a[2], input.b[2], input.r[3], hi_0_idx);
1400 apply_nnf_selectors(NNF_SELECTORS::NON_NATIVE_FIELD_3);
1401 this->increment_num_gates();
1402
1403 //
1404 // Gate 6: NNF gate with no constraints (q_nnf=0, truly unconstrained)
1405 // Provides values a[1], b[1], r[2], hi_1 to Gate 5 via shifts (w'1, w'2, w'3, w'4)
1406 //
1407 blocks.nnf.populate_wires(input.a[1], input.b[1], input.r[2], hi_1_idx);
1408 apply_nnf_selectors(NNF_SELECTORS::NNF_NONE);
1409 this->increment_num_gates();
1410
1411 //
1412 // Gate 7: big_add_gate to validate hi_2
1413 //
1414 // hi_2 - hi_1 - lo_1 - q[2](p[1].2^b + p[0]) - q[3](p[0].2^b) = 0
1415 //
1416 create_big_add_gate(
1417 {
1418 input.q[2],
1419 input.q[3],
1420 lo_1_idx,
1421 hi_1_idx,
1422 -input.neg_modulus[1] * LIMB_SHIFT - input.neg_modulus[0],
1423 -input.neg_modulus[0] * LIMB_SHIFT,
1424 -1,
1425 -1,
1426 0,
1427 },
1428 /*include_next_gate_w_4*/ true);
1429
1430 //
1431 // Gate 8: big_add_gate to validate hi_3 (provides hi_2 in w_4 for gate 7)
1432 //
1433 // hi_3 - (hi_2 - q[0](p[3].2^b + p[2]) - q[1](p[2].2^b + p[1])).2^-2b = 0
1434 //
1435 create_big_add_gate({
1436 hi_3_idx,
1437 input.q[0],
1438 input.q[1],
1439 hi_2_idx,
1440 -1,
1441 input.neg_modulus[3] * LIMB_RSHIFT + input.neg_modulus[2] * LIMB_RSHIFT_2,
1442 input.neg_modulus[2] * LIMB_RSHIFT + input.neg_modulus[1] * LIMB_RSHIFT_2,
1443 LIMB_RSHIFT_2,
1444 0,
1445 });
1446
1447 return std::array<uint32_t, 2>{ lo_1_idx, hi_3_idx };
1448}
1449
1457{
1458 for (size_t i = 0; i < cached_partial_non_native_field_multiplications.size(); ++i) {
1459 auto& c = cached_partial_non_native_field_multiplications[i];
1460 for (size_t j = 0; j < c.a.size(); ++j) {
1461 c.a[j] = this->real_variable_index[c.a[j]];
1462 c.b[j] = this->real_variable_index[c.b[j]];
1463 }
1464 }
1465 cached_partial_non_native_field_multiplication::deduplicate(cached_partial_non_native_field_multiplications, this);
1466
1467 // iterate over the cached items and create constraints
1468 for (const auto& input : cached_partial_non_native_field_multiplications) {
1469
1470 blocks.nnf.populate_wires(input.a[1], input.b[1], this->zero_idx(), input.lo_0);
1471 apply_nnf_selectors(NNF_SELECTORS::NON_NATIVE_FIELD_1);
1472 this->increment_num_gates();
1473
1474 blocks.nnf.populate_wires(input.a[0], input.b[0], input.a[3], input.b[3]);
1475 apply_nnf_selectors(NNF_SELECTORS::NON_NATIVE_FIELD_2);
1476 this->increment_num_gates();
1477
1478 blocks.nnf.populate_wires(input.a[2], input.b[2], this->zero_idx(), input.hi_0);
1479 apply_nnf_selectors(NNF_SELECTORS::NON_NATIVE_FIELD_3);
1480 this->increment_num_gates();
1481
1482 blocks.nnf.populate_wires(input.a[1], input.b[1], this->zero_idx(), input.hi_1);
1483 apply_nnf_selectors(NNF_SELECTORS::NNF_NONE);
1484 this->increment_num_gates();
1485 }
1486}
1487
1494template <typename ExecutionTrace>
1497{
1499 this->get_variable(input.a[0]),
1500 this->get_variable(input.a[1]),
1501 this->get_variable(input.a[2]),
1502 this->get_variable(input.a[3]),
1503 };
1505 this->get_variable(input.b[0]),
1506 this->get_variable(input.b[1]),
1507 this->get_variable(input.b[2]),
1508 this->get_variable(input.b[3]),
1509 };
1510
1511 constexpr FF LIMB_SHIFT = uint256_t(1) << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS;
1512
1513 FF lo_0 = a[0] * b[0] + ((a[1] * b[0] + a[0] * b[1]) * LIMB_SHIFT);
1514 FF hi_0 = a[2] * b[0] + a[0] * b[2] + ((a[0] * b[3] + a[3] * b[0]) * LIMB_SHIFT);
1515 FF hi_1 = hi_0 + a[1] * b[1] + ((a[1] * b[2] + a[2] * b[1]) * LIMB_SHIFT);
1516
1517 const uint32_t lo_0_idx = this->add_variable(lo_0);
1518 const uint32_t hi_0_idx = this->add_variable(hi_0);
1519 const uint32_t hi_1_idx = this->add_variable(hi_1);
1520
1521 // Add witnesses into the multiplication cache (duplicates removed during circuit finalization)
1523 .a = input.a,
1524 .b = input.b,
1525 .lo_0 = lo_0_idx,
1526 .hi_0 = hi_0_idx,
1527 .hi_1 = hi_1_idx,
1528 };
1529 cached_partial_non_native_field_multiplications.emplace_back(cache_entry);
1530 return std::array<uint32_t, 2>{ lo_0_idx, hi_1_idx };
1531}
1532
1538template <typename ExecutionTrace>
1541{
1542 const uint32_t& x_0 = std::get<0>(limb0).first;
1543 const uint32_t& x_1 = std::get<0>(limb1).first;
1544 const uint32_t& x_2 = std::get<0>(limb2).first;
1545 const uint32_t& x_3 = std::get<0>(limb3).first;
1546 const uint32_t& x_p = std::get<0>(limbp);
1547
1548 const FF& x_mulconst0 = std::get<0>(limb0).second;
1549 const FF& x_mulconst1 = std::get<0>(limb1).second;
1550 const FF& x_mulconst2 = std::get<0>(limb2).second;
1551 const FF& x_mulconst3 = std::get<0>(limb3).second;
1552
1553 const uint32_t& y_0 = std::get<1>(limb0).first;
1554 const uint32_t& y_1 = std::get<1>(limb1).first;
1555 const uint32_t& y_2 = std::get<1>(limb2).first;
1556 const uint32_t& y_3 = std::get<1>(limb3).first;
1557 const uint32_t& y_p = std::get<1>(limbp);
1558
1559 const FF& y_mulconst0 = std::get<1>(limb0).second;
1560 const FF& y_mulconst1 = std::get<1>(limb1).second;
1561 const FF& y_mulconst2 = std::get<1>(limb2).second;
1562 const FF& y_mulconst3 = std::get<1>(limb3).second;
1563
1564 // constant additive terms
1565 const FF& addconst0 = std::get<2>(limb0);
1566 const FF& addconst1 = std::get<2>(limb1);
1567 const FF& addconst2 = std::get<2>(limb2);
1568 const FF& addconst3 = std::get<2>(limb3);
1569 const FF& addconstp = std::get<2>(limbp);
1570
1571 // get value of result limbs
1572 const FF z_0value = (this->get_variable(x_0) * x_mulconst0) + (this->get_variable(y_0) * y_mulconst0) + addconst0;
1573 const FF z_1value = (this->get_variable(x_1) * x_mulconst1) + (this->get_variable(y_1) * y_mulconst1) + addconst1;
1574 const FF z_2value = (this->get_variable(x_2) * x_mulconst2) + (this->get_variable(y_2) * y_mulconst2) + addconst2;
1575 const FF z_3value = (this->get_variable(x_3) * x_mulconst3) + (this->get_variable(y_3) * y_mulconst3) + addconst3;
1576 const FF z_pvalue = this->get_variable(x_p) + this->get_variable(y_p) + addconstp;
1577
1578 const uint32_t z_0 = this->add_variable(z_0value);
1579 const uint32_t z_1 = this->add_variable(z_1value);
1580 const uint32_t z_2 = this->add_variable(z_2value);
1581 const uint32_t z_3 = this->add_variable(z_3value);
1582 const uint32_t z_p = this->add_variable(z_pvalue);
1583
1605 auto& block = blocks.arithmetic;
1606 block.populate_wires(y_p, x_0, y_0, x_p);
1607 block.populate_wires(z_p, x_1, y_1, z_0);
1608 block.populate_wires(x_2, y_2, z_2, z_1);
1609 block.populate_wires(x_3, y_3, z_3, this->zero_idx());
1610
1611 // When q_arith == 3, w_4_shift is scaled by 2 (see ArithmeticRelation for details). Therefore, for consistency we
1612 // also scale each linear term by this factor of 2 so that the constraint is effectively:
1613 // (q_l * w_1) + (q_r * w_2) + (q_o * w_3) + (q_4 * w_4) + q_c + w_4_shift = 0
1614 const FF linear_term_scale_factor = 2;
1615 block.q_m().emplace_back(addconstp);
1616 block.q_1().emplace_back(0);
1617 block.q_2().emplace_back(-x_mulconst0 * linear_term_scale_factor);
1618 block.q_3().emplace_back(-y_mulconst0 * linear_term_scale_factor);
1619 block.q_4().emplace_back(0);
1620 block.q_c().emplace_back(-addconst0 * linear_term_scale_factor);
1621 block.set_gate_selector(3);
1622
1623 block.q_m().emplace_back(0);
1624 block.q_1().emplace_back(0);
1625 block.q_2().emplace_back(-x_mulconst1);
1626 block.q_3().emplace_back(-y_mulconst1);
1627 block.q_4().emplace_back(0);
1628 block.q_c().emplace_back(-addconst1);
1629 block.set_gate_selector(2);
1630
1631 block.q_m().emplace_back(0);
1632 block.q_1().emplace_back(-x_mulconst2);
1633 block.q_2().emplace_back(-y_mulconst2);
1634 block.q_3().emplace_back(1);
1635 block.q_4().emplace_back(0);
1636 block.q_c().emplace_back(-addconst2);
1637 block.set_gate_selector(1);
1638
1639 block.q_m().emplace_back(0);
1640 block.q_1().emplace_back(-x_mulconst3);
1641 block.q_2().emplace_back(-y_mulconst3);
1642 block.q_3().emplace_back(1);
1643 block.q_4().emplace_back(0);
1644 block.q_c().emplace_back(-addconst3);
1645 block.set_gate_selector(1);
1646
1647 check_selector_length_consistency();
1648
1649 this->increment_num_gates(4);
1651 z_0, z_1, z_2, z_3, z_p,
1652 };
1653}
1654
1660template <typename ExecutionTrace>
1663{
1664 const uint32_t& x_0 = std::get<0>(limb0).first;
1665 const uint32_t& x_1 = std::get<0>(limb1).first;
1666 const uint32_t& x_2 = std::get<0>(limb2).first;
1667 const uint32_t& x_3 = std::get<0>(limb3).first;
1668 const uint32_t& x_p = std::get<0>(limbp);
1669
1670 const FF& x_mulconst0 = std::get<0>(limb0).second;
1671 const FF& x_mulconst1 = std::get<0>(limb1).second;
1672 const FF& x_mulconst2 = std::get<0>(limb2).second;
1673 const FF& x_mulconst3 = std::get<0>(limb3).second;
1674
1675 const uint32_t& y_0 = std::get<1>(limb0).first;
1676 const uint32_t& y_1 = std::get<1>(limb1).first;
1677 const uint32_t& y_2 = std::get<1>(limb2).first;
1678 const uint32_t& y_3 = std::get<1>(limb3).first;
1679 const uint32_t& y_p = std::get<1>(limbp);
1680
1681 const FF& y_mulconst0 = std::get<1>(limb0).second;
1682 const FF& y_mulconst1 = std::get<1>(limb1).second;
1683 const FF& y_mulconst2 = std::get<1>(limb2).second;
1684 const FF& y_mulconst3 = std::get<1>(limb3).second;
1685
1686 // constant additive terms
1687 const FF& addconst0 = std::get<2>(limb0);
1688 const FF& addconst1 = std::get<2>(limb1);
1689 const FF& addconst2 = std::get<2>(limb2);
1690 const FF& addconst3 = std::get<2>(limb3);
1691 const FF& addconstp = std::get<2>(limbp);
1692
1693 // get value of result limbs
1694 const FF z_0value = (this->get_variable(x_0) * x_mulconst0) - (this->get_variable(y_0) * y_mulconst0) + addconst0;
1695 const FF z_1value = (this->get_variable(x_1) * x_mulconst1) - (this->get_variable(y_1) * y_mulconst1) + addconst1;
1696 const FF z_2value = (this->get_variable(x_2) * x_mulconst2) - (this->get_variable(y_2) * y_mulconst2) + addconst2;
1697 const FF z_3value = (this->get_variable(x_3) * x_mulconst3) - (this->get_variable(y_3) * y_mulconst3) + addconst3;
1698 const FF z_pvalue = this->get_variable(x_p) - this->get_variable(y_p) + addconstp;
1699
1700 const uint32_t z_0 = this->add_variable(z_0value);
1701 const uint32_t z_1 = this->add_variable(z_1value);
1702 const uint32_t z_2 = this->add_variable(z_2value);
1703 const uint32_t z_3 = this->add_variable(z_3value);
1704 const uint32_t z_p = this->add_variable(z_pvalue);
1705
1730 auto& block = blocks.arithmetic;
1731 block.populate_wires(y_p, x_0, y_0, z_p);
1732 block.populate_wires(x_p, x_1, y_1, z_0);
1733 block.populate_wires(x_2, y_2, z_2, z_1);
1734 block.populate_wires(x_3, y_3, z_3, this->zero_idx());
1735
1736 // When q_arith == 3, w_4_shift is scaled by 2 (see ArithmeticRelation for details). Therefore, for consistency we
1737 // also scale each linear term by this factor of 2 so that the constraint is effectively:
1738 // (q_l * w_1) + (q_r * w_2) + (q_o * w_3) + (q_4 * w_4) + q_c + w_4_shift = 0
1739 const FF linear_term_scale_factor = 2;
1740 block.q_m().emplace_back(-addconstp);
1741 block.q_1().emplace_back(0);
1742 block.q_2().emplace_back(-x_mulconst0 * linear_term_scale_factor);
1743 block.q_3().emplace_back(y_mulconst0 * linear_term_scale_factor);
1744 block.q_4().emplace_back(0);
1745 block.q_c().emplace_back(-addconst0 * linear_term_scale_factor);
1746 block.set_gate_selector(3);
1747
1748 block.q_m().emplace_back(0);
1749 block.q_1().emplace_back(0);
1750 block.q_2().emplace_back(-x_mulconst1);
1751 block.q_3().emplace_back(y_mulconst1);
1752 block.q_4().emplace_back(0);
1753 block.q_c().emplace_back(-addconst1);
1754 block.set_gate_selector(2);
1755
1756 block.q_m().emplace_back(0);
1757 block.q_1().emplace_back(-x_mulconst2);
1758 block.q_2().emplace_back(y_mulconst2);
1759 block.q_3().emplace_back(1);
1760 block.q_4().emplace_back(0);
1761 block.q_c().emplace_back(-addconst2);
1762 block.set_gate_selector(1);
1763
1764 block.q_m().emplace_back(0);
1765 block.q_1().emplace_back(-x_mulconst3);
1766 block.q_2().emplace_back(y_mulconst3);
1767 block.q_3().emplace_back(1);
1768 block.q_4().emplace_back(0);
1769 block.q_c().emplace_back(-addconst3);
1770 block.set_gate_selector(1);
1771
1772 check_selector_length_consistency();
1773
1774 this->increment_num_gates(4);
1776 z_0, z_1, z_2, z_3, z_p,
1777 };
1778}
1779
1789template <typename ExecutionTrace>
1791{
1792 return this->rom_ram_logic.create_ROM_array(array_size);
1793}
1794
1804template <typename ExecutionTrace>
1806{
1807 return this->rom_ram_logic.create_RAM_array(array_size);
1808}
1809
1817template <typename ExecutionTrace>
1819 const size_t index_value,
1820 const uint32_t value_witness)
1821{
1822 this->rom_ram_logic.init_RAM_element(this, ram_id, index_value, value_witness);
1823}
1824
1825template <typename ExecutionTrace>
1826uint32_t UltraCircuitBuilder_<ExecutionTrace>::read_RAM_array(const size_t ram_id, const uint32_t index_witness)
1827{
1828 return this->rom_ram_logic.read_RAM_array(this, ram_id, index_witness);
1829}
1830
1831template <typename ExecutionTrace>
1833 const uint32_t index_witness,
1834 const uint32_t value_witness)
1835{
1836 this->rom_ram_logic.write_RAM_array(this, ram_id, index_witness, value_witness);
1837}
1838
1854template <typename ExecutionTrace>
1856 const size_t index_value,
1857 const uint32_t value_witness)
1858{
1859 this->rom_ram_logic.set_ROM_element(this, rom_id, index_value, value_witness);
1860}
1861
1869template <typename ExecutionTrace>
1871 const size_t index_value,
1872 const std::array<uint32_t, 2>& value_witnesses)
1873{
1874 this->rom_ram_logic.set_ROM_element_pair(this, rom_id, index_value, value_witnesses);
1875}
1876
1884template <typename ExecutionTrace>
1885uint32_t UltraCircuitBuilder_<ExecutionTrace>::read_ROM_array(const size_t rom_id, const uint32_t index_witness)
1886{
1887 return this->rom_ram_logic.read_ROM_array(this, rom_id, index_witness);
1888}
1889
1897template <typename ExecutionTrace>
1898std::array<uint32_t, 2> UltraCircuitBuilder_<ExecutionTrace>::read_ROM_array_pair(const size_t rom_id,
1899 const uint32_t index_witness)
1900{
1901 return this->rom_ram_logic.read_ROM_array_pair(this, rom_id, index_witness);
1902}
1903
1907template <typename FF>
1909{
1910 auto& block = this->blocks.poseidon2_external;
1911 block.populate_wires(in.a, in.b, in.c, in.d);
1912 block.q_m().emplace_back(0);
1916 block.q_c().emplace_back(0);
1918 block.set_gate_selector(1);
1919 this->check_selector_length_consistency();
1920 this->increment_num_gates();
1921}
1922
1926template <typename FF>
1928{
1929 auto& block = this->blocks.poseidon2_internal;
1930 block.populate_wires(in.a, in.b, in.c, in.d);
1931 block.q_m().emplace_back(0);
1933 block.q_2().emplace_back(0);
1934 block.q_3().emplace_back(0);
1935 block.q_c().emplace_back(0);
1936 block.q_4().emplace_back(0);
1937 block.set_gate_selector(1);
1938 this->check_selector_length_consistency();
1939 this->increment_num_gates();
1940}
1941
1948template <typename ExecutionTrace> msgpack::sbuffer UltraCircuitBuilder_<ExecutionTrace>::export_circuit()
1949{
1950 // You should not name `zero` by yourself
1951 // but it will be rewritten anyway
1952 auto first_zero_idx = this->get_first_variable_in_class(this->zero_idx());
1953 if (!this->variable_names.contains(first_zero_idx)) {
1954 this->set_variable_name(this->zero_idx(), "zero");
1955 } else {
1956 this->variable_names[first_zero_idx] = "zero";
1957 }
1958 using base = CircuitBuilderBase<FF>;
1960
1961 std::array<uint64_t, 4> modulus = {
1962 FF::Params::modulus_0, FF::Params::modulus_1, FF::Params::modulus_2, FF::Params::modulus_3
1963 };
1964 std::stringstream buf;
1965 buf << std::hex << std::setfill('0') << std::setw(16) << modulus[3] << std::setw(16) << modulus[2] << std::setw(16)
1966 << modulus[1] << std::setw(16) << modulus[0];
1967
1968 cir.modulus = buf.str();
1969
1970 for (uint32_t i = 0; i < this->num_public_inputs(); i++) {
1971 cir.public_inps.push_back(this->real_variable_index[this->public_inputs()[i]]);
1972 }
1973
1974 for (auto& tup : base::variable_names) {
1975 cir.vars_of_interest.insert({ this->real_variable_index[tup.first], tup.second });
1976 }
1977
1978 for (const auto& var : this->get_variables()) {
1979 cir.variables.push_back(var);
1980 }
1981
1982 FF curve_b;
1983 if constexpr (FF::modulus == bb::fq::modulus) {
1984 curve_b = bb::g1::curve_b;
1985 } else if constexpr (FF::modulus == grumpkin::fq::modulus) {
1986 curve_b = grumpkin::g1::curve_b;
1987 } else {
1988 curve_b = 0;
1989 }
1990
1991 for (auto& block : blocks.get()) {
1992 std::vector<std::vector<FF>> block_selectors;
1994 for (size_t idx = 0; idx < block.size(); ++idx) {
1995 std::vector<FF> tmp_sel = { block.q_m()[idx],
1996 block.q_1()[idx],
1997 block.q_2()[idx],
1998 block.q_3()[idx],
1999 block.q_4()[idx],
2000 block.q_c()[idx],
2001 block.q_arith()[idx],
2002 block.q_delta_range()[idx],
2003 block.q_elliptic()[idx],
2004 block.q_memory()[idx],
2005 block.q_nnf()[idx],
2006 block.q_lookup()[idx],
2007 curve_b };
2008
2009 std::vector<uint32_t> tmp_w = {
2010 this->real_variable_index[block.w_l()[idx]],
2011 this->real_variable_index[block.w_r()[idx]],
2012 this->real_variable_index[block.w_o()[idx]],
2013 this->real_variable_index[block.w_4()[idx]],
2014 };
2015
2016 if (idx < block.size() - 1) {
2017 tmp_w.push_back(block.w_l()[idx + 1]);
2018 tmp_w.push_back(block.w_r()[idx + 1]);
2019 tmp_w.push_back(block.w_o()[idx + 1]);
2020 tmp_w.push_back(block.w_4()[idx + 1]);
2021 } else {
2022 tmp_w.push_back(0);
2023 tmp_w.push_back(0);
2024 tmp_w.push_back(0);
2025 tmp_w.push_back(0);
2026 }
2027
2028 block_selectors.push_back(tmp_sel);
2029 block_wires.push_back(tmp_w);
2030 }
2031 cir.selectors.push_back(block_selectors);
2032 cir.wires.push_back(block_wires);
2033 }
2034
2035 cir.real_variable_index = this->real_variable_index;
2036
2037 for (const auto& table : this->lookup_tables) {
2038 const FF table_index(table.table_index);
2039 info("Table no: ", table.table_index);
2040 std::vector<std::vector<FF>> tmp_table;
2041 for (size_t i = 0; i < table.size(); ++i) {
2042 tmp_table.push_back({ table.column_1[i], table.column_2[i], table.column_3[i] });
2043 }
2044 cir.lookup_tables.push_back(tmp_table);
2045 }
2046
2047 cir.real_variable_tags = this->real_variable_tags;
2048
2049 for (const auto& list : range_lists) {
2050 cir.range_tags[list.second.range_tag] = list.first;
2051 }
2052
2053 for (auto& rom_table : this->rom_ram_logic.rom_arrays) {
2054 std::sort(rom_table.records.begin(), rom_table.records.end());
2055
2057 table.reserve(rom_table.records.size());
2058 for (const auto& rom_entry : rom_table.records) {
2059 table.push_back({
2060 this->real_variable_index[rom_entry.index_witness],
2061 this->real_variable_index[rom_entry.value_column1_witness],
2062 this->real_variable_index[rom_entry.value_column2_witness],
2063 });
2064 }
2065 cir.rom_records.push_back(table);
2066 cir.rom_states.push_back(rom_table.state);
2067 }
2068
2069 for (auto& ram_table : this->rom_ram_logic.ram_arrays) {
2070 std::sort(ram_table.records.begin(), ram_table.records.end());
2071
2073 table.reserve(ram_table.records.size());
2074 for (const auto& ram_entry : ram_table.records) {
2075 table.push_back({ this->real_variable_index[ram_entry.index_witness],
2076 this->real_variable_index[ram_entry.value_witness],
2077 this->real_variable_index[ram_entry.timestamp_witness],
2078 ram_entry.access_type });
2079 }
2080 cir.ram_records.push_back(table);
2081 cir.ram_states.push_back(ram_table.state);
2082 }
2083
2084 cir.circuit_finalized = this->circuit_finalized;
2085
2086 msgpack::sbuffer buffer;
2087 msgpack::pack(buffer, cir);
2088 return buffer;
2089}
2090
2093
2094} // namespace bb
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
#define BB_ASSERT_GTE(left, right,...)
Definition assert.hpp:128
#define BB_ASSERT_GT(left, right,...)
Definition assert.hpp:113
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:83
#define BB_ASSERT_LTE(left, right,...)
Definition assert.hpp:158
bb::field< bb::Bn254FrParams > FF
Definition field.cpp:24
#define BB_BENCH_NAME(name)
Definition bb_bench.hpp:225
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 create_sort_constraint_with_edges(const std::vector< uint32_t > &variable_indices, const FF &start, const FF &end)
Constrain consecutive variable differences to be in {0, 1, 2, 3}, with boundary checks.
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_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....
msgpack::sbuffer export_circuit() override
void create_small_range_constraint(const uint32_t variable_index, const uint64_t target_range, std::string const msg="create_small_range_constraint")
Range-constraints for small ranges, where the upper bound (target_range) need not be dyadic....
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_add_gate(const add_triple_< FF > &in)
Create an addition gate, where in.a * in.a_scaling + in.b * in.b_scaling + in.c * in....
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)
Construct gates for non-native field addition.
std::vector< uint32_t > create_limbed_range_constraint(const uint32_t variable_index, const uint64_t num_bits, const uint64_t target_range_bitnum=DEFAULT_PLOOKUP_RANGE_BITNUM, std::string const &msg="create_limbed_range_constraint")
Range-constrain a variable to [0, 2^num_bits - 1] by decomposing into smaller limbs.
size_t create_ROM_array(const size_t array_size)
Create a new read-only memory region (a.k.a. ROM table)
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)
Create gates from pre-computed accumulator values which simultaneously establish individual basic-tab...
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_ecc_add_gate(const ecc_add_gate_< FF > &in)
Create an elliptic curve addition gate.
void finalize_circuit(const bool ensure_nonzero)
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)
Create gates for a full non-native field multiplication identity a * b = q * p + r.
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 enforce_small_deltas(const std::vector< uint32_t > &variable_indices)
Check for a sequence of variables that the neighboring differences are in {0, 1, 2,...
void create_bool_gate(const uint32_t a)
Generate an arithmetic gate equivalent to x^2 - x = 0, which forces x to be 0 or 1.
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)
Queue the addition of gates constraining the limb-multiplication part of a non native field mul.
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)
Construct gates for non-native field subtraction.
void apply_memory_selectors(const MEMORY_SELECTORS type)
Enable the memory gate of particular type.
void process_non_native_field_multiplications()
Iterates over the cached_non_native_field_multiplication objects, removes duplicates,...
void create_arithmetic_gate(const arithmetic_triple_< FF > &in)
A plonk gate with disabled (set to zero) fourth wire. q_m * a * b + q_1 * a + q_2 * b + q_3.
static constexpr Fq curve_b
Definition group.hpp:51
constexpr uint64_t get_msb() const
Container for lookup accumulator values and table reads.
Definition types.hpp:357
std::vector< BasicTable::LookupEntry > lookup_entries
Definition types.hpp:363
#define info(...)
Definition log.hpp:93
FF a
FF b
uint8_t const * buf
Definition data_store.hpp:9
uint8_t buffer[RANDOM_BUFFER_SIZE]
Definition engine.cpp:48
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:125
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.
Definition api.hpp:5
field< Bn254FrParams > fr
Definition fr.hpp:174
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 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:285
std::vector< LookupEntry > lookup_gates
Definition types.hpp:321
size_t size() const
Definition types.hpp:332
std::vector< bb::fr > column_3
Definition types.hpp:320
std::vector< bb::fr > column_2
Definition types.hpp:319
std::vector< bb::fr > column_1
Definition types.hpp:318