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> void UltraCircuitBuilder_<ExecutionTrace>::finalize_circuit()
27{
53 if (!this->circuit_finalized) {
54 process_non_native_field_multiplications();
55#ifndef ULTRA_FUZZ
56 this->rom_ram_logic.process_ROM_arrays(this);
57 this->rom_ram_logic.process_RAM_arrays(this);
58 process_range_lists();
59#endif
60 populate_public_inputs_block();
61 this->circuit_finalized = true;
62 } else {
63 // Gates added after first call to finalize will not be processed since finalization is only performed once
64 info("WARNING: Redundant call to finalize_circuit(). Is this intentional?");
65 }
66}
67
72{
73 BB_BENCH_NAME("populate_public_inputs_block");
74
75 // Update the public inputs block
76 for (const auto& idx : this->public_inputs()) {
77 // first two wires get a copy of the public inputs
78 blocks.pub_inputs.populate_wires(idx, idx, this->zero_idx(), this->zero_idx());
79 for (auto& selector : this->blocks.pub_inputs.get_selectors()) {
80 selector.emplace_back(0);
81 }
82 }
83}
84
93template <typename ExecutionTrace> void UltraCircuitBuilder_<ExecutionTrace>::create_add_gate(const add_triple_<FF>& in)
94{
95 // Delegate to create_big_add_gate with 4th wire set to zero
96 create_big_add_gate({ .a = in.a,
97 .b = in.b,
98 .c = in.c,
99 .d = this->zero_idx(),
100 .a_scaling = in.a_scaling,
101 .b_scaling = in.b_scaling,
102 .c_scaling = in.c_scaling,
103 .d_scaling = 0,
104 .const_scaling = in.const_scaling });
105}
106
115template <typename ExecutionTrace>
117 const bool include_next_gate_w_4)
118{
119 this->assert_valid_variables({ in.a, in.b, in.c, in.d });
120 blocks.arithmetic.populate_wires(in.a, in.b, in.c, in.d);
121 // If include_next_gate_w_4 is true then we set q_arith = 2. In this case, the linear term in the ArithmeticRelation
122 // is scaled by a factor of 2. We compensate here by scaling the quadratic term by 2 to achieve the constraint:
123 // 2 * [q_m * w_1 * w_2 + \sum_{i=1..4} q_i * w_i + q_c + w_4_shift] = 0
124 const FF mul_scaling = include_next_gate_w_4 ? in.mul_scaling * FF(2) : in.mul_scaling;
125 blocks.arithmetic.q_m().emplace_back(mul_scaling);
126 blocks.arithmetic.q_1().emplace_back(in.a_scaling);
127 blocks.arithmetic.q_2().emplace_back(in.b_scaling);
128 blocks.arithmetic.q_3().emplace_back(in.c_scaling);
129 blocks.arithmetic.q_c().emplace_back(in.const_scaling);
130 blocks.arithmetic.q_4().emplace_back(in.d_scaling);
131 blocks.arithmetic.q_5().emplace_back(0);
132 blocks.arithmetic.set_gate_selector(include_next_gate_w_4 ? 2 : 1);
133 check_selector_length_consistency();
134 this->increment_num_gates();
135}
136
145template <typename ExecutionTrace>
147 const bool include_next_gate_w_4)
148{
149 this->assert_valid_variables({ in.a, in.b, in.c, in.d });
150 blocks.arithmetic.populate_wires(in.a, in.b, in.c, in.d);
151 blocks.arithmetic.q_m().emplace_back(0);
152 blocks.arithmetic.q_1().emplace_back(in.a_scaling);
153 blocks.arithmetic.q_2().emplace_back(in.b_scaling);
154 blocks.arithmetic.q_3().emplace_back(in.c_scaling);
155 blocks.arithmetic.q_c().emplace_back(in.const_scaling);
156 blocks.arithmetic.q_4().emplace_back(in.d_scaling);
157 blocks.arithmetic.q_5().emplace_back(0);
158 blocks.arithmetic.set_gate_selector(include_next_gate_w_4 ? 2 : 1);
159 check_selector_length_consistency();
160 this->increment_num_gates();
161}
162
168template <typename ExecutionTrace>
170{
171 this->assert_valid_variables({ variable_index });
172
173 blocks.arithmetic.populate_wires(variable_index, variable_index, this->zero_idx(), this->zero_idx());
174 blocks.arithmetic.q_m().emplace_back(1);
175 blocks.arithmetic.q_1().emplace_back(-1);
176 blocks.arithmetic.q_2().emplace_back(0);
177 blocks.arithmetic.q_3().emplace_back(0);
178 blocks.arithmetic.q_c().emplace_back(0);
179 blocks.arithmetic.q_4().emplace_back(0);
180 blocks.arithmetic.q_5().emplace_back(0);
181 blocks.arithmetic.set_gate_selector(1);
182 check_selector_length_consistency();
183 this->increment_num_gates();
184}
185
192template <typename ExecutionTrace>
194{
195 this->assert_valid_variables({ in.a, in.b, in.c });
196
197 blocks.arithmetic.populate_wires(in.a, in.b, in.c, this->zero_idx());
198 blocks.arithmetic.q_m().emplace_back(in.q_m);
199 blocks.arithmetic.q_1().emplace_back(in.q_l);
200 blocks.arithmetic.q_2().emplace_back(in.q_r);
201 blocks.arithmetic.q_3().emplace_back(in.q_o);
202 blocks.arithmetic.q_c().emplace_back(in.q_c);
203 blocks.arithmetic.q_4().emplace_back(0);
204 blocks.arithmetic.q_5().emplace_back(0);
205 blocks.arithmetic.set_gate_selector(1);
206 check_selector_length_consistency();
207 this->increment_num_gates();
208}
209
226template <typename ExecutionTrace>
228{
229 this->assert_valid_variables({ in.x1, in.x2, in.x3, in.y1, in.y2, in.y3 });
230
231 auto& block = blocks.elliptic;
232
233 // Convert bool to field element for the relation: +1 for addition, -1 for subtraction
234 // The elliptic curve relation assumes q_sign² = 1 (see elliptic_relation.hpp)
235 const FF q_sign = in.is_addition ? FF(1) : FF(-1);
236
237 // Determine whether we can fuse this addition operation into the previous gate in the block
238 bool can_fuse_into_previous_gate =
239 block.size() > 0 && /* a previous gate exists in the block */
240 block.w_r()[block.size() - 1] == in.x1 && /* output x coord of previous gate is input of this one */
241 block.w_o()[block.size() - 1] == in.y1; /* output y coord of previous gate is input of this one */
242
243 if (can_fuse_into_previous_gate) {
244 block.q_1().set(block.size() - 1, q_sign); // set q_sign of previous gate
245 block.gate_selector_for(GateKind::Elliptic).set(block.size() - 1, 1); // set q_ecc of previous gate to 1
246 } else {
247 block.populate_wires(this->zero_idx(), in.x1, in.y1, this->zero_idx());
248 block.q_3().emplace_back(0);
249 block.q_4().emplace_back(0);
250 block.q_5().emplace_back(0);
251 block.q_1().emplace_back(q_sign);
252
253 block.q_2().emplace_back(0);
254 block.q_m().emplace_back(0);
255 block.q_c().emplace_back(0);
256 block.set_gate_selector(1);
257 check_selector_length_consistency();
258 this->increment_num_gates();
259 }
260 // Create the unconstrained gate with the output of the doubling to be read into by the previous gate via shifts
261 create_unconstrained_gate(block, in.x2, in.x3, in.y3, in.y2);
262}
263
280template <typename ExecutionTrace>
282{
283 this->assert_valid_variables({ in.x1, in.x3, in.y1, in.y3 });
284
285 auto& block = blocks.elliptic;
286
287 // Determine whether we can fuse this doubling operation into the previous gate in the block
288 bool can_fuse_into_previous_gate =
289 block.size() > 0 && /* a previous gate exists in the block */
290 block.w_r()[block.size() - 1] == in.x1 && /* output x coord of previous gate is input of this one */
291 block.w_o()[block.size() - 1] == in.y1; /* output y coord of previous gate is input of this one */
292
293 // If possible, update the previous gate to be the first gate in the pair, otherwise create a new gate
294 if (can_fuse_into_previous_gate) {
295 block.gate_selector_for(GateKind::Elliptic).set(block.size() - 1, 1); // set q_ecc of previous gate to 1
296 block.q_m().set(block.size() - 1, 1); // set q_m (q_is_double) of previous gate to 1
297 } else {
298 block.populate_wires(this->zero_idx(), in.x1, in.y1, this->zero_idx());
299 block.q_m().emplace_back(1);
300 block.q_1().emplace_back(0);
301 block.q_2().emplace_back(0);
302 block.q_3().emplace_back(0);
303 block.q_c().emplace_back(0);
304 block.q_4().emplace_back(0);
305 block.q_5().emplace_back(0);
306 block.set_gate_selector(1);
307 check_selector_length_consistency();
308 this->increment_num_gates();
309 }
310 // Create the unconstrained gate with the output of the doubling to be read into by the previous gate via shifts
311 create_unconstrained_gate(block, this->zero_idx(), in.x3, in.y3, this->zero_idx());
312}
313
320template <typename ExecutionTrace>
321void UltraCircuitBuilder_<ExecutionTrace>::fix_witness(const uint32_t witness_index, const FF& witness_value)
322{
323 this->assert_valid_variables({ witness_index });
324
325 // Mark as intentionally single-gate for boomerang detection
326 update_used_witnesses(witness_index);
327
328 blocks.arithmetic.populate_wires(witness_index, this->zero_idx(), this->zero_idx(), this->zero_idx());
329 blocks.arithmetic.q_m().emplace_back(0);
330 blocks.arithmetic.q_1().emplace_back(1);
331 blocks.arithmetic.q_2().emplace_back(0);
332 blocks.arithmetic.q_3().emplace_back(0);
333 blocks.arithmetic.q_c().emplace_back(-witness_value);
334 blocks.arithmetic.q_4().emplace_back(0);
335 blocks.arithmetic.q_5().emplace_back(0);
336 blocks.arithmetic.set_gate_selector(1);
337 check_selector_length_consistency();
338 this->increment_num_gates();
339}
340
341template <typename ExecutionTrace>
343{
344 if (constant_variable_indices.contains(variable)) {
345 return constant_variable_indices.at(variable);
346 } else {
347 uint32_t variable_index = this->add_variable(variable);
348 fix_witness(variable_index, variable);
349 constant_variable_indices.insert({ variable, variable_index });
350 return variable_index;
351 }
352}
353
362template <typename ExecutionTrace>
364{
365 for (plookup::BasicTable& table : lookup_tables) {
366 if (table.id == id) {
367 return table;
368 }
369 }
370 // Table doesn't exist! So try to create it.
371 lookup_tables.emplace_back(plookup::create_basic_table(id, lookup_tables.size()));
372 return lookup_tables.back();
373}
374
376template <typename ExecutionTrace>
378{
379 table.table_index = lookup_tables.size();
380 lookup_tables.emplace_back(std::move(table));
381 return &lookup_tables.back();
382}
383
385template <typename ExecutionTrace>
387 const uint32_t val1_idx,
388 const uint32_t val2_idx,
389 plookup::BasicTable& table,
391 const FF column_1_step_size,
392 const FF column_2_step_size,
393 const FF column_3_step_size)
394{
395 this->assert_valid_variables({ key_idx, val1_idx, val2_idx });
396
397 table.lookup_gates.emplace_back(entry);
398
399 blocks.lookup.populate_wires(key_idx, val1_idx, val2_idx, this->zero_idx());
400 blocks.lookup.set_gate_selector(1);
401 blocks.lookup.q_3().emplace_back(FF(table.table_index));
402 blocks.lookup.q_2().emplace_back(column_1_step_size);
403 blocks.lookup.q_m().emplace_back(column_2_step_size);
404 blocks.lookup.q_c().emplace_back(column_3_step_size);
405 blocks.lookup.q_1().emplace_back(0);
406 blocks.lookup.q_4().emplace_back(0);
407 blocks.lookup.q_5().emplace_back(0);
408
409 check_selector_length_consistency();
410 this->increment_num_gates();
411}
412
440template <typename ExecutionTrace>
442 const plookup::MultiTableId& id,
443 const plookup::ReadData<FF>& read_values,
444 const uint32_t key_a_index,
445 std::optional<uint32_t> key_b_index)
446{
447 using plookup::ColumnIdx;
448
449 const auto& multi_table = plookup::get_multitable(id);
450 const size_t num_lookups = read_values[ColumnIdx::C1].size();
452
453 for (size_t i = 0; i < num_lookups; ++i) {
454 const bool is_first_lookup = (i == 0);
455 const bool is_last_lookup = (i == num_lookups - 1);
456
457 // Get basic lookup table; construct and add to builder.lookup_tables if not already present
458 plookup::BasicTable& table = get_table(multi_table.basic_table_ids[i]);
459
460 // Create witness variables: first lookup reuses user's input indices, subsequent create new variables
461 const auto first_idx = is_first_lookup ? key_a_index : this->add_variable(read_values[ColumnIdx::C1][i]);
462 const auto second_idx = (is_first_lookup && key_b_index.has_value())
463 ? *key_b_index
464 : this->add_variable(read_values[ColumnIdx::C2][i]);
465 const auto third_idx = this->add_variable(read_values[ColumnIdx::C3][i]);
466
467 read_data[ColumnIdx::C1].push_back(first_idx);
468 read_data[ColumnIdx::C2].push_back(second_idx);
469 read_data[ColumnIdx::C3].push_back(third_idx);
470
471 // Step size coefficients: zero for last lookup (no next accumulator), negative step sizes otherwise
472 const FF col1_step = is_last_lookup ? FF(0) : -multi_table.column_1_step_sizes[i + 1];
473 const FF col2_step = is_last_lookup ? FF(0) : -multi_table.column_2_step_sizes[i + 1];
474 const FF col3_step = is_last_lookup ? FF(0) : -multi_table.column_3_step_sizes[i + 1];
475
476 create_lookup_gate(
477 first_idx, second_idx, third_idx, table, read_values.lookup_entries[i], col1_step, col2_step, col3_step);
478 }
479 return read_data;
480}
481
485template <typename ExecutionTrace>
487 const uint64_t target_range)
488{
489 RangeList result;
490 const auto range_tag = get_new_tag();
491 const auto tau_tag = get_new_tag();
492 set_tau_transposition(range_tag, tau_tag);
493 result.target_range = target_range;
494 result.range_tag = range_tag;
495 result.tau_tag = tau_tag;
496
497 uint64_t num_multiples_of_three = (target_range / DEFAULT_PLOOKUP_RANGE_STEP_SIZE);
498 // allocate the minimum number of variable indices required for the range constraint. this function is only called
499 // when we are creating a range constraint on a witness index, which is responsible for the extra + 1. (note that
500 // the below loop goes from 0 to `num_multiples_of_three` inclusive.)
501 result.variable_indices.reserve(static_cast<uint32_t>(num_multiples_of_three + 3));
502 for (uint64_t i = 0; i <= num_multiples_of_three; ++i) {
503 const uint32_t index = this->add_variable(fr(i * DEFAULT_PLOOKUP_RANGE_STEP_SIZE));
504 result.variable_indices.emplace_back(index);
505 assign_tag(index, result.range_tag);
506 }
507 // `target_range` may not be divisible by 3, so we explicitly add it also.
508 {
509 const uint32_t index = this->add_variable(fr(target_range));
510 result.variable_indices.emplace_back(index);
511 assign_tag(index, result.range_tag);
512 }
513 // Need this because these variables will not appear in the witness otherwise
514 create_unconstrained_gates(result.variable_indices);
515
516 return result;
517}
518
519template <typename ExecutionTrace>
521 const uint32_t variable_index, const uint64_t num_bits, const uint64_t target_range_bitnum, std::string const& msg)
523 this->assert_valid_variables({ variable_index });
524 // make sure `num_bits` satisfies the correct bounds
525 BB_ASSERT_GT(num_bits, 0U);
526 BB_ASSERT_GTE(MAX_NUM_BITS_RANGE_CONSTRAINT, num_bits);
527
528 uint256_t val = (uint256_t)(this->get_variable(variable_index));
529
530 // If the value is out of range, set the CircuitBuilder error to the given msg.
531 if (val.get_msb() >= num_bits && !this->failed()) {
532 this->failure(msg);
533 }
534
535 // compute limb structure
536 const uint64_t sublimb_mask = (1ULL << target_range_bitnum) - 1;
537
538 std::vector<uint64_t> sublimbs;
539 std::vector<uint32_t> sublimb_indices;
540
541 const bool has_remainder_bits = (num_bits % target_range_bitnum != 0);
542 const uint64_t num_limbs = (num_bits / target_range_bitnum) + has_remainder_bits;
543 const uint64_t last_limb_size = num_bits - ((num_bits / target_range_bitnum) * target_range_bitnum);
544 const uint64_t last_limb_range = ((uint64_t)1 << last_limb_size) - 1;
545
546 // extract limbs from the value
547 uint256_t accumulator = val;
548 for (size_t i = 0; i < num_limbs; ++i) {
549 sublimbs.push_back(accumulator.data[0] & sublimb_mask);
550 accumulator = accumulator >> target_range_bitnum;
551 }
552 // set the correct range constraint on each limb. note that when there are remainder bits, the last limb must be
553 // constrained to a smaller range.
554 const size_t num_full_limbs = has_remainder_bits ? sublimbs.size() - 1 : sublimbs.size();
555 for (size_t i = 0; i < num_full_limbs; ++i) {
556 const auto limb_idx = this->add_variable(bb::fr(sublimbs[i]));
557 sublimb_indices.emplace_back(limb_idx);
558 create_small_range_constraint(limb_idx, sublimb_mask);
559 }
560 if (has_remainder_bits) {
561 const auto limb_idx = this->add_variable(bb::fr(sublimbs.back()));
562 sublimb_indices.emplace_back(limb_idx);
563 create_small_range_constraint(limb_idx, last_limb_range);
564 }
565
566 // Prove that the limbs reconstruct the original value by processing limbs in groups of 3.
567 // We constrain: value = sum_{j=0}^{num_limbs-1} limb[j] * 2^(j * target_range_bitnum)
568 //
569 // Each iteration subtracts 3 limbs' contributions from an accumulator (starting at `val`),
570 // and constrains that the accumulator updates correctly via an arithmetic gate.
571 const uint64_t num_limb_triples = (num_limbs / 3) + ((num_limbs % 3) != 0);
572 // `leftovers` is the number of real limbs in the final triple (1, 2, or 3).
573 const uint64_t leftovers = (num_limbs % 3) == 0 ? 3 : (num_limbs % 3);
574
575 accumulator = val;
576 uint32_t accumulator_idx = variable_index;
577 // loop goes from `i = 0` to `num_limb_triples`, but some special case must be taken for the last triple (`i ==
578 // num_limb_triples - 1`), hence some conditional logic.
579 for (size_t i = 0; i < num_limb_triples; ++i) {
580 // `real_limbs` which limb positions in this triple contain actual limbs vs zero-padding.
581 // When `i == num_limb_triples - 1`, some positions may be unused if `num_limbs` isn't divisible by 3.
582 const bool real_limbs[3]{
583 !(i == (num_limb_triples - 1) && (leftovers < 1)),
584 !(i == (num_limb_triples - 1) && (leftovers < 2)),
585 !(i == (num_limb_triples - 1) && (leftovers < 3)),
586 };
587
588 // The witness values of the 3 limbs in this triple (0 for padding positions).
589 const uint64_t round_sublimbs[3]{
590 real_limbs[0] ? sublimbs[3 * i] : 0,
591 real_limbs[1] ? sublimbs[3 * i + 1] : 0,
592 real_limbs[2] ? sublimbs[3 * i + 2] : 0,
593 };
594 // The witnesss indices of the current 3 limbs (zero_idx for padding positions).
595 const uint32_t new_limbs[3]{
596 real_limbs[0] ? sublimb_indices[3 * i] : this->zero_idx(),
597 real_limbs[1] ? sublimb_indices[3 * i + 1] : this->zero_idx(),
598 real_limbs[2] ? sublimb_indices[3 * i + 2] : this->zero_idx(),
599 };
600 // Bit-shifts for each limb: limb[3*i+k] contributes at bit position (3*i+k) * target_range_bitnum.
601 const uint64_t shifts[3]{
602 target_range_bitnum * (3 * i),
603 target_range_bitnum * (3 * i + 1),
604 target_range_bitnum * (3 * i + 2),
605 };
606 // Compute the new accumulator after subtracting this triple's contribution.
607 // After the final iteration, accumulator should be 0.
608 uint256_t new_accumulator = accumulator - (uint256_t(round_sublimbs[0]) << shifts[0]) -
609 (uint256_t(round_sublimbs[1]) << shifts[1]) -
610 (uint256_t(round_sublimbs[2]) << shifts[2]);
611
612 // This `big_add_gate` has differing behavior depending on whether or not `i == num_limb_triples - 1`.
613 // If `i != num_limb_triples - 1`, then the constraint will be limb[0]*2^shift[0] + limb[1]*2^shift[1] +
614 // limb[2]*2^shift[2] - acc = new_accumulator (the last argument to `create_big_add_gate` is `true`, means the
615 // sum is w_4-shift, which will be the witness corresponding to what is currently `new_accumulator`.).
616 // If `i == num_limb_triples - 1`, then the last argument to `create_big_add_gate` is false, so the constraint
617 // is limb[0]*2^shift[0] + limb[1]*2^shift[1] + limb[2]*2^shift[2] - acc = 0.
618 //
619 // N.B. When `num_bits` is small, we only have remainder bits. This last constraint, checking the correctness of
620 // the limb-decomposition, ensures that the variable is not orphaned. (See the warning in
621 // `create_small_range_constraint`.)
622 create_big_add_gate(
624 new_limbs[0],
625 new_limbs[1],
626 new_limbs[2],
627 accumulator_idx,
628 uint256_t(1) << shifts[0],
629 uint256_t(1) << shifts[1],
630 uint256_t(1) << shifts[2],
631 -1,
632 0,
633 },
634 (i != num_limb_triples - 1));
635 if (i != num_limb_triples - 1) {
636 accumulator_idx = this->add_variable(fr(new_accumulator));
637 accumulator = new_accumulator;
638 }
639 }
640 return sublimb_indices;
641}
642
643template <typename ExecutionTrace>
645 const uint64_t target_range,
646 std::string const msg)
647{
648 // make sure `target_range` is not too big.
649 BB_ASSERT_GTE(MAX_SMALL_RANGE_CONSTRAINT_VAL, target_range);
650 const bool is_out_of_range = (uint256_t(this->get_variable(variable_index)).data[0] > target_range);
651 if (is_out_of_range && !this->failed()) {
652 this->failure(msg);
653 }
654 if (range_lists.count(target_range) == 0) {
655 range_lists.insert({ target_range, create_range_list(target_range) });
656 }
657 // The tag of `variable_index` is `DEFAULT_TAG` if it has never been range-constrained and a non-trivial value
658 // otherwise.
659 const auto existing_tag = this->real_variable_tags[this->real_variable_index[variable_index]];
660 auto& list = range_lists[target_range];
661
662 // If the variable's tag matches the target range list's tag, do nothing; the variable has _already_ been
663 // constrained to this exact range (i.e., `create_new_range_constraint(variable_index, target_range)` has already
664 // been called).
665 if (existing_tag == list.range_tag) {
666 return;
667 }
668 // If the variable is 'untagged' (i.e., it has the dummy tag), assign it the appropriate tag, which amounts to
669 // setting the range-constraint.
670 if (existing_tag == DEFAULT_TAG) {
671 assign_tag(variable_index, list.range_tag);
672 list.variable_indices.emplace_back(variable_index);
673 return;
674 }
675 // Otherwise, find the range for which the variable has already been tagged.
676 bool found_tag = false;
677 for (const auto& r : range_lists) {
678 if (r.second.range_tag == existing_tag) {
679 found_tag = true;
680 if (r.first < target_range) {
681 // The variable already has a more restrictive range check, so do nothing.
682 return;
683 }
684 // The range constraint we are trying to impose is more restrictive than the existing range
685 // constraint. It would be difficult to remove an existing range check. Instead, arithmetically copy the
686 // variable and apply a range check to new variable. We do _not_ simply create a
687 // copy-constraint, because that would copy the tag, which exactly corresponds to the old (less
688 // restrictive) range constraint. Instead, we use an arithmetic gate to constrain the value of
689 // the new variable and set the tag (a.k.a. range-constraint) via a new call to
690 // `create_new_range_constraint`.
691 const uint32_t copied_witness = this->add_variable(this->get_variable(variable_index));
692 create_add_gate({ .a = variable_index,
693 .b = copied_witness,
694 .c = this->zero_idx(),
695 .a_scaling = 1,
696 .b_scaling = -1,
697 .c_scaling = 0,
698 .const_scaling = 0 });
699 // Recurse with new witness that has no tag attached.
700 create_small_range_constraint(copied_witness, target_range, msg);
701 return;
702 }
703 }
704 // should never occur
705 BB_ASSERT(found_tag);
706}
707
708template <typename ExecutionTrace> void UltraCircuitBuilder_<ExecutionTrace>::process_range_list(RangeList& list)
709{
710 this->assert_valid_variables(list.variable_indices);
711
712 BB_ASSERT_GT(list.variable_indices.size(), 0U);
713
714 // replace witness-index in variable_indices with the corresponding real-variable-index i.e., if a copy constraint
715 // has been applied on a variable after it was range constrained, this makes sure the indices in list point to the
716 // updated index in the range list so the set equivalence does not fail
717 for (uint32_t& x : list.variable_indices) {
718 x = this->real_variable_index[x];
719 }
720 // Sort `variable_indices` and remove duplicate witness indices to prevent the sorted list set size being wrong!
721 std::sort(list.variable_indices.begin(), list.variable_indices.end());
722 auto back_iterator = std::unique(list.variable_indices.begin(), list.variable_indices.end());
723 list.variable_indices.erase(back_iterator, list.variable_indices.end());
724
725 // Extract the values of each (real) variable into a list to be sorted (in the sense of the range/plookup-style
726 // argument).
727 std::vector<uint32_t> sorted_list;
728 sorted_list.reserve(list.variable_indices.size());
729 for (const auto variable_index : list.variable_indices) {
730 // note that `field_element` is < 32 bits as the corresponding witness has a non-trivial range-constraint.
731 const auto& field_element = this->get_variable(variable_index);
732 const uint32_t shrinked_value = static_cast<uint32_t>(field_element);
733 sorted_list.emplace_back(shrinked_value);
734 }
735
736#ifdef NO_PAR_ALGOS
737 std::sort(sorted_list.begin(), sorted_list.end());
738#else
739 std::sort(std::execution::par_unseq, sorted_list.begin(), sorted_list.end());
740#endif
741 // list must be padded to a multipe of 4 and larger than 4 (gate_width)
742 constexpr size_t gate_width = NUM_WIRES;
743 size_t padding = (gate_width - (list.variable_indices.size() % gate_width)) % gate_width;
744
745 std::vector<uint32_t> indices;
746 indices.reserve(padding + sorted_list.size());
747
748 if (list.variable_indices.size() <= gate_width) {
749 padding += gate_width;
750 }
751 for (size_t i = 0; i < padding; ++i) {
752 indices.emplace_back(this->zero_idx());
753 }
754 // tag the elements in the sorted_list to apply the multiset-equality check implicit in range-constraints.
755 for (const auto sorted_value : sorted_list) {
756 const uint32_t index = this->add_variable(fr(sorted_value));
757 assign_tag(index, list.tau_tag);
758 indices.emplace_back(index);
759 }
760 // constrain the _sorted_ list: starts at 0, ends at `target_range`, consecutive differences in {0, 1, 2, 3}.
761 create_sort_constraint_with_edges(indices, 0, list.target_range);
762}
763
764template <typename ExecutionTrace> void UltraCircuitBuilder_<ExecutionTrace>::process_range_lists()
765{
766 for (auto& i : range_lists) {
767 process_range_list(i.second);
768 }
769}
770
771template <typename ExecutionTrace>
772void UltraCircuitBuilder_<ExecutionTrace>::enforce_small_deltas(const std::vector<uint32_t>& variable_indices)
773{
774 constexpr size_t gate_width = NUM_WIRES;
775 BB_ASSERT_EQ(variable_indices.size() % gate_width, 0U);
776 this->assert_valid_variables(variable_indices);
777
778 for (size_t i = 0; i < variable_indices.size(); i += gate_width) {
779 blocks.delta_range.populate_wires(
780 variable_indices[i], variable_indices[i + 1], variable_indices[i + 2], variable_indices[i + 3]);
781
782 this->increment_num_gates();
783 blocks.delta_range.q_m().emplace_back(0);
784 blocks.delta_range.q_1().emplace_back(0);
785 blocks.delta_range.q_2().emplace_back(0);
786 blocks.delta_range.q_3().emplace_back(0);
787 blocks.delta_range.q_c().emplace_back(0);
788 blocks.delta_range.q_4().emplace_back(0);
789 blocks.delta_range.q_5().emplace_back(0);
790 blocks.delta_range.set_gate_selector(1);
791 check_selector_length_consistency();
792 }
793 // dummy gate needed because of widget's check of next row
794 create_unconstrained_gate(blocks.delta_range,
795 variable_indices[variable_indices.size() - 1],
796 this->zero_idx(),
797 this->zero_idx(),
798 this->zero_idx());
799}
800
801// useful to put variables in the witness that aren't already used - e.g. the dummy variables of the range constraint in
802// multiples of four
803template <typename ExecutionTrace>
804void UltraCircuitBuilder_<ExecutionTrace>::create_unconstrained_gates(const std::vector<uint32_t>& variable_index)
805{
806 std::vector<uint32_t> padded_list = variable_index;
807 constexpr size_t gate_width = NUM_WIRES;
808 const uint64_t padding = (gate_width - (padded_list.size() % gate_width)) % gate_width;
809 for (uint64_t i = 0; i < padding; ++i) {
810 padded_list.emplace_back(this->zero_idx());
811 }
812 this->assert_valid_variables(variable_index);
813 this->assert_valid_variables(padded_list);
814
815 for (size_t i = 0; i < padded_list.size(); i += gate_width) {
816 create_unconstrained_gate(
817 blocks.arithmetic, padded_list[i], padded_list[i + 1], padded_list[i + 2], padded_list[i + 3]);
818 }
819}
820
821template <typename ExecutionTrace>
823 const std::vector<uint32_t>& variable_indices, const FF& start, const FF& end)
824{
825 // Convenient to assume size is at least 8 (gate_width = 4) for separate gates for start and end conditions
826 constexpr size_t gate_width = NUM_WIRES;
827 BB_ASSERT_EQ(variable_indices.size() % gate_width, 0U);
828 BB_ASSERT_GT(variable_indices.size(), gate_width);
829 this->assert_valid_variables(variable_indices);
830 // 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`
831 // to be in {0, 1, 2, 3}.
832 auto& block = blocks.delta_range;
833
834 // Add an arithmetic gate to ensure the first input is equal to the start value of the range being checked
835 create_add_gate({ variable_indices[0], this->zero_idx(), this->zero_idx(), 1, 0, 0, -start });
836
837 // enforce delta range relation for all rows (there are `variabe_indices.size() / gate_width`). note that there are
838 // at least two rows.
839 for (size_t i = 0; i < variable_indices.size(); i += gate_width) {
840
841 block.populate_wires(
842 variable_indices[i], variable_indices[i + 1], variable_indices[i + 2], variable_indices[i + 3]);
843 this->increment_num_gates();
844 block.q_m().emplace_back(0);
845 block.q_1().emplace_back(0);
846 block.q_2().emplace_back(0);
847 block.q_3().emplace_back(0);
848 block.q_c().emplace_back(0);
849 block.q_4().emplace_back(0);
850 block.q_5().emplace_back(0);
851 block.set_gate_selector(1);
852 check_selector_length_consistency();
853 }
854
855 // 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}).
856 // Therefore, we repeat the last element in an unconstrained gate.
857 create_unconstrained_gate(
858 block, variable_indices[variable_indices.size() - 1], this->zero_idx(), this->zero_idx(), this->zero_idx());
859 // arithmetic gate to constrain that `variable_indices[last] == end`, i.e., verify the boundary condition.
860 create_add_gate(
861 { variable_indices[variable_indices.size() - 1], this->zero_idx(), this->zero_idx(), 1, 0, 0, -end });
862}
863
886template <typename ExecutionTrace>
888{
889 auto& block = blocks.memory;
890 block.set_gate_selector(type == MEMORY_SELECTORS::MEM_NONE ? 0 : 1);
891 switch (type) {
892 case MEMORY_SELECTORS::ROM_CONSISTENCY_CHECK: {
893 // Memory read gate used with the sorted list of memory reads.
894 // Apply sorted memory read checks with the following additional check:
895 // 1. Assert that if index field across two gates does not change, the value field does not change.
896 // Used for ROM reads and RAM reads across write/read boundaries
897 block.q_1().emplace_back(1);
898 block.q_2().emplace_back(1);
899 block.q_3().emplace_back(0);
900 block.q_4().emplace_back(0);
901 block.q_5().emplace_back(0);
902 block.q_m().emplace_back(0);
903 block.q_c().emplace_back(0);
904 check_selector_length_consistency();
905 break;
906 }
907 case MEMORY_SELECTORS::RAM_CONSISTENCY_CHECK: {
908 // Memory read gate used with the sorted list of memory reads.
909 // 1. Validate adjacent index values across 2 gates increases by 0 or 1
910 // 2. Validate record computation (r = read_write_flag + index * \eta + \timestamp * \eta^2 + value * \eta^3)
911 // 3. If adjacent index values across 2 gates does not change, and the next gate's read_write_flag is set to
912 // 'read', validate adjacent values do not change Used for ROM reads and RAM reads across read/write boundaries
913 block.q_1().emplace_back(0);
914 block.q_2().emplace_back(0);
915 block.q_3().emplace_back(1);
916 block.q_4().emplace_back(0);
917 block.q_5().emplace_back(0);
918 block.q_m().emplace_back(0);
919 block.q_c().emplace_back(0);
920 check_selector_length_consistency();
921 break;
922 }
923 case MEMORY_SELECTORS::RAM_TIMESTAMP_CHECK: {
924 // For two adjacent RAM entries that share the same index, validate the timestamp value is monotonically
925 // increasing
926 block.q_1().emplace_back(1);
927 block.q_2().emplace_back(0);
928 block.q_3().emplace_back(0);
929 block.q_4().emplace_back(1);
930 block.q_5().emplace_back(0);
931 block.q_m().emplace_back(0);
932 block.q_c().emplace_back(0);
933 check_selector_length_consistency();
934 break;
935 }
936 case MEMORY_SELECTORS::ROM_READ: {
937 // Memory read gate for reading memory cells. Also used for the _initialization_ of ROM memory cells.
938 // Validates record witness computation (r = read_write_flag + index * \eta + timestamp * \eta^2 + value *
939 // \eta^3)
940 block.q_1().emplace_back(1);
941 block.q_2().emplace_back(0);
942 block.q_3().emplace_back(0);
943 block.q_4().emplace_back(0);
944 block.q_5().emplace_back(0);
945 block.q_m().emplace_back(1); // validate record witness is correctly computed
946 block.q_c().emplace_back(0); // read/write flag stored in q_c
947 check_selector_length_consistency();
948 break;
949 }
950 case MEMORY_SELECTORS::RAM_READ: {
951 // Memory read gate for reading memory cells.
952 // Validates record witness computation (r = read_write_flag + index * \eta + timestamp * \eta^2 + value *
953 // \eta^3)
954 block.q_1().emplace_back(1);
955 block.q_2().emplace_back(0);
956 block.q_3().emplace_back(0);
957 block.q_4().emplace_back(0);
958 block.q_5().emplace_back(0);
959 block.q_m().emplace_back(1); // validate record witness is correctly computed
960 block.q_c().emplace_back(0); // read/write flag stored in q_c
961 check_selector_length_consistency();
962 break;
963 }
964 case MEMORY_SELECTORS::RAM_WRITE: {
965 // Memory read gate for writing memory cells.
966 // Validates record witness computation (r = read_write_flag + index * \eta + timestamp * \eta^2 + value *
967 // \eta^3)
968 block.q_1().emplace_back(1);
969 block.q_2().emplace_back(0);
970 block.q_3().emplace_back(0);
971 block.q_4().emplace_back(0);
972 block.q_5().emplace_back(0);
973 block.q_m().emplace_back(1); // validate record witness is correctly computed
974 block.q_c().emplace_back(1); // read/write flag stored in q_c
975 check_selector_length_consistency();
976 break;
977 }
978 default: {
979 block.q_1().emplace_back(0);
980 block.q_2().emplace_back(0);
981 block.q_3().emplace_back(0);
982 block.q_4().emplace_back(0);
983 block.q_5().emplace_back(0);
984 block.q_m().emplace_back(0);
985 block.q_c().emplace_back(0);
986 check_selector_length_consistency();
987 break;
988 }
989 }
990}
991
1015template <typename ExecutionTrace>
1017{
1018 auto& block = blocks.nnf;
1019 block.set_gate_selector(type == NNF_SELECTORS::NNF_NONE ? 0 : 1);
1020 switch (type) {
1021 case NNF_SELECTORS::LIMB_ACCUMULATE_1: {
1022 block.q_1().emplace_back(0);
1023 block.q_2().emplace_back(0);
1024 block.q_3().emplace_back(1);
1025 block.q_4().emplace_back(1);
1026 block.q_5().emplace_back(0);
1027 block.q_m().emplace_back(0);
1028 block.q_c().emplace_back(0);
1029 check_selector_length_consistency();
1030 break;
1031 }
1032 case NNF_SELECTORS::LIMB_ACCUMULATE_2: {
1033 block.q_1().emplace_back(0);
1034 block.q_2().emplace_back(0);
1035 block.q_3().emplace_back(1);
1036 block.q_4().emplace_back(0);
1037 block.q_5().emplace_back(0);
1038 block.q_m().emplace_back(1);
1039 block.q_c().emplace_back(0);
1040 check_selector_length_consistency();
1041 break;
1042 }
1043 case NNF_SELECTORS::NON_NATIVE_FIELD_1: {
1044 block.q_1().emplace_back(0);
1045 block.q_2().emplace_back(1);
1046 block.q_3().emplace_back(1);
1047 block.q_4().emplace_back(0);
1048 block.q_5().emplace_back(0);
1049 block.q_m().emplace_back(0);
1050 block.q_c().emplace_back(0);
1051 check_selector_length_consistency();
1052 break;
1053 }
1054 case NNF_SELECTORS::NON_NATIVE_FIELD_2: {
1055 block.q_1().emplace_back(0);
1056 block.q_2().emplace_back(1);
1057 block.q_3().emplace_back(0);
1058 block.q_4().emplace_back(1);
1059 block.q_5().emplace_back(0);
1060 block.q_m().emplace_back(0);
1061 block.q_c().emplace_back(0);
1062 check_selector_length_consistency();
1063 break;
1064 }
1065 case NNF_SELECTORS::NON_NATIVE_FIELD_3: {
1066 block.q_1().emplace_back(0);
1067 block.q_2().emplace_back(1);
1068 block.q_3().emplace_back(0);
1069 block.q_4().emplace_back(0);
1070 block.q_5().emplace_back(0);
1071 block.q_m().emplace_back(1);
1072 block.q_c().emplace_back(0);
1073 check_selector_length_consistency();
1074 break;
1075 }
1076 default: {
1077 block.q_1().emplace_back(0);
1078 block.q_2().emplace_back(0);
1079 block.q_3().emplace_back(0);
1080 block.q_4().emplace_back(0);
1081 block.q_5().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
1100template <typename ExecutionTrace>
1102 const uint32_t hi_idx,
1103 const size_t lo_limb_bits,
1104 const size_t hi_limb_bits,
1105 std::string const& msg)
1106{
1107 // Validate limbs are <= 70 bits. If limbs are larger we require more witnesses and cannot use our limb accumulation
1108 // custom gate
1109 BB_ASSERT_LTE(lo_limb_bits, 14U * 5U);
1110 BB_ASSERT_LTE(hi_limb_bits, 14U * 5U);
1111
1112 // If the value is larger than the range, we log the error in builder
1113 const bool is_lo_out_of_range = (uint256_t(this->get_variable(lo_idx)) >= (uint256_t(1) << lo_limb_bits));
1114 if (is_lo_out_of_range && !this->failed()) {
1115 this->failure(msg + ": lo limb.");
1116 }
1117 const bool is_hi_out_of_range = (uint256_t(this->get_variable(hi_idx)) >= (uint256_t(1) << hi_limb_bits));
1118 if (is_hi_out_of_range && !this->failed()) {
1119 this->failure(msg + ": hi limb.");
1120 }
1121
1122 // Sometimes we try to use limbs that are too large. It's easier to catch this issue here
1123 const auto get_sublimbs = [&](const uint32_t& limb_idx, const std::array<uint64_t, 5>& sublimb_masks) {
1124 const uint256_t limb = this->get_variable(limb_idx);
1125 // we can use constant 2^14 - 1 mask here. If the sublimb value exceeds the expected value then witness will
1126 // fail the range check below
1127 // We also use zero_idx to substitute variables that should be zero
1128 constexpr uint256_t MAX_SUBLIMB_MASK = (uint256_t(1) << 14) - 1;
1129 std::array<uint32_t, 5> sublimb_indices;
1130 sublimb_indices[0] = sublimb_masks[0] != 0 ? this->add_variable(fr(limb & MAX_SUBLIMB_MASK)) : this->zero_idx();
1131 sublimb_indices[1] =
1132 sublimb_masks[1] != 0 ? this->add_variable(fr((limb >> 14) & MAX_SUBLIMB_MASK)) : this->zero_idx();
1133 sublimb_indices[2] =
1134 sublimb_masks[2] != 0 ? this->add_variable(fr((limb >> 28) & MAX_SUBLIMB_MASK)) : this->zero_idx();
1135 sublimb_indices[3] =
1136 sublimb_masks[3] != 0 ? this->add_variable(fr((limb >> 42) & MAX_SUBLIMB_MASK)) : this->zero_idx();
1137 sublimb_indices[4] =
1138 sublimb_masks[4] != 0 ? this->add_variable(fr((limb >> 56) & MAX_SUBLIMB_MASK)) : this->zero_idx();
1139 return sublimb_indices;
1140 };
1141
1142 const auto get_limb_masks = [](size_t limb_bits) {
1143 std::array<uint64_t, 5> sublimb_masks;
1144 sublimb_masks[0] = limb_bits >= 14 ? 14 : limb_bits;
1145 sublimb_masks[1] = limb_bits >= 28 ? 14 : (limb_bits > 14 ? limb_bits - 14 : 0);
1146 sublimb_masks[2] = limb_bits >= 42 ? 14 : (limb_bits > 28 ? limb_bits - 28 : 0);
1147 sublimb_masks[3] = limb_bits >= 56 ? 14 : (limb_bits > 42 ? limb_bits - 42 : 0);
1148 sublimb_masks[4] = (limb_bits > 56 ? limb_bits - 56 : 0);
1149
1150 for (auto& mask : sublimb_masks) {
1151 mask = (1ULL << mask) - 1ULL;
1152 }
1153 return sublimb_masks;
1154 };
1155
1156 const auto lo_masks = get_limb_masks(lo_limb_bits);
1157 const auto hi_masks = get_limb_masks(hi_limb_bits);
1158 const std::array<uint32_t, 5> lo_sublimbs = get_sublimbs(lo_idx, lo_masks);
1159 const std::array<uint32_t, 5> hi_sublimbs = get_sublimbs(hi_idx, hi_masks);
1160
1161 blocks.nnf.populate_wires(lo_sublimbs[0], lo_sublimbs[1], lo_sublimbs[2], lo_idx);
1162 blocks.nnf.populate_wires(lo_sublimbs[3], lo_sublimbs[4], hi_sublimbs[0], hi_sublimbs[1]);
1163 blocks.nnf.populate_wires(hi_sublimbs[2], hi_sublimbs[3], hi_sublimbs[4], hi_idx);
1164
1165 apply_nnf_selectors(NNF_SELECTORS::LIMB_ACCUMULATE_1);
1166 apply_nnf_selectors(NNF_SELECTORS::LIMB_ACCUMULATE_2);
1167 apply_nnf_selectors(NNF_SELECTORS::NNF_NONE);
1168 this->increment_num_gates(3);
1169
1170 for (size_t i = 0; i < 5; i++) {
1171 if (lo_masks[i] != 0) {
1172 create_small_range_constraint(
1173 lo_sublimbs[i], lo_masks[i], "ultra_circuit_builder: sublimb of low too large");
1174 }
1175 if (hi_masks[i] != 0) {
1176 create_small_range_constraint(
1177 hi_sublimbs[i], hi_masks[i], "ultra_circuit_builder: sublimb of hi too large");
1178 }
1179 }
1180};
1181
1197template <typename ExecutionTrace>
1200{
1201 const auto [a0, a1, a2, a3] = std::array{ this->get_variable(input.a[0]),
1202 this->get_variable(input.a[1]),
1203 this->get_variable(input.a[2]),
1204 this->get_variable(input.a[3]) };
1205 const auto [b0, b1, b2, b3] = std::array{ this->get_variable(input.b[0]),
1206 this->get_variable(input.b[1]),
1207 this->get_variable(input.b[2]),
1208 this->get_variable(input.b[3]) };
1209 const auto [q0, q1, q2, q3] = std::array{ this->get_variable(input.q[0]),
1210 this->get_variable(input.q[1]),
1211 this->get_variable(input.q[2]),
1212 this->get_variable(input.q[3]) };
1213 const auto [r0, r1, r2, r3] = std::array{ this->get_variable(input.r[0]),
1214 this->get_variable(input.r[1]),
1215 this->get_variable(input.r[2]),
1216 this->get_variable(input.r[3]) };
1217 const auto& p_neg = input.neg_modulus;
1218
1219 constexpr FF LIMB_SHIFT = uint256_t(1) << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS;
1220 constexpr FF LIMB_RSHIFT = FF(1) / FF(uint256_t(1) << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS);
1221 constexpr FF LIMB_RSHIFT_2 = FF(1) / FF(uint256_t(1) << (2 * DEFAULT_NON_NATIVE_FIELD_LIMB_BITS));
1222
1223 // lo_0 = (a0·b0 - r0) + (a1·b0 + a0·b1)·2^L
1224 FF lo_0 = (a0 * b0 - r0) + (a1 * b0 + a0 * b1) * LIMB_SHIFT;
1225 // lo_1 = (lo_0 + q0·p0' + (q1·p0' + q0·p1' - r1)·2^L) / 2^2L
1226 FF lo_1 = (lo_0 + q0 * p_neg[0] + (q1 * p_neg[0] + q0 * p_neg[1] - r1) * LIMB_SHIFT) * LIMB_RSHIFT_2;
1227
1228 // hi_0 = (a2·b0 + a0·b2) + (a0·b3 + a3·b0 - r3)·2^L
1229 FF hi_0 = (a2 * b0 + a0 * b2) + (a0 * b3 + a3 * b0 - r3) * LIMB_SHIFT;
1230 // hi_1 = hi_0 + (a1·b1 - r2) + (a1·b2 + a2·b1)·2^L
1231 FF hi_1 = hi_0 + (a1 * b1 - r2) + (a1 * b2 + a2 * b1) * LIMB_SHIFT;
1232 // hi_2 = hi_1 + lo_1 + q2·p0' + (q3·p0' + q2·p1')·2^L
1233 FF hi_2 = hi_1 + lo_1 + q2 * p_neg[0] + (q3 * p_neg[0] + q2 * p_neg[1]) * LIMB_SHIFT;
1234 // hi_3 = (hi_2 + q0·p2' + q1·p1' + (q0·p3' + q1·p2')·2^L) / 2^2L
1235 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;
1236
1237 const uint32_t lo_0_idx = this->add_variable(lo_0);
1238 const uint32_t lo_1_idx = this->add_variable(lo_1);
1239 const uint32_t hi_0_idx = this->add_variable(hi_0);
1240 const uint32_t hi_1_idx = this->add_variable(hi_1);
1241 const uint32_t hi_2_idx = this->add_variable(hi_2);
1242 const uint32_t hi_3_idx = this->add_variable(hi_3);
1243
1244 // Gate 1: big_add_gate to validate lo_1
1245 // (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
1246 // This constraint requires two rows in the trace: an arithmetic gate plus an unconstrained arithmetic gate
1247 // containing lo_0 in wire 4 so that the previous gate can access it via shifts. (We cannot use the next nnf gate
1248 // for this purpose since our trace is sorted by gate type).
1249 create_big_add_gate({ input.q[0],
1250 input.q[1],
1251 input.r[1],
1252 lo_1_idx,
1253 input.neg_modulus[0] + input.neg_modulus[1] * LIMB_SHIFT,
1254 input.neg_modulus[0] * LIMB_SHIFT,
1255 -LIMB_SHIFT,
1256 -LIMB_SHIFT.sqr(),
1257 0 },
1258 /*include_next_gate_w_4*/ true);
1259 // Gate 2: unconstrained gate to provide lo_0 via w_4_shift for gate 1
1260 create_unconstrained_gate(blocks.arithmetic, this->zero_idx(), this->zero_idx(), this->zero_idx(), lo_0_idx);
1261
1262 //
1263 // a = (a3 || a2 || a1 || a0) = (a3 * 2^b + a2) * 2^b + (a1 * 2^b + a0)
1264 // b = (b3 || b2 || b1 || b0) = (b3 * 2^b + b2) * 2^b + (b1 * 2^b + b0)
1265 //
1266 // Gate 3: NNF gate to check if lo_0 was computed correctly
1267 // The gate structure for the nnf gates is as follows:
1268 //
1269 // | a1 | b1 | r0 | lo_0 | <-- Gate 3: check lo_0
1270 // | a0 | b0 | a3 | b3 |
1271 // | a2 | b2 | r3 | hi_0 |
1272 // | a1 | b1 | r2 | hi_1 |
1273 //
1274 // Constraint: lo_0 = (a1 * b0 + a0 * b1) * 2^b + (a0 * b0) - r0
1275 // w4 = (w1 * w'2 + w'1 * w2) * 2^b + (w'1 * w'2) - w3
1276 //
1277 blocks.nnf.populate_wires(input.a[1], input.b[1], input.r[0], lo_0_idx);
1278 apply_nnf_selectors(NNF_SELECTORS::NON_NATIVE_FIELD_1);
1279 this->increment_num_gates();
1280
1281 //
1282 // Gate 4: NNF gate to check if hi_0 was computed correctly
1283 //
1284 // | a1 | b1 | r0 | lo_0 |
1285 // | a0 | b0 | a3 | b3 | <-- Gate 4: check hi_0
1286 // | a2 | b2 | r3 | hi_0 |
1287 // | a1 | b1 | r2 | hi_1 |
1288 //
1289 // Constraint: hi_0 = (a0 * b3 + a3 * b0 - r3) * 2^b + (a0 * b2 + a2 * b0)
1290 // w'4 = (w1 * w4 + w2 * w3 - w'3) * 2^b + (w1 * w'2 + w'1 * w2)
1291 //
1292 blocks.nnf.populate_wires(input.a[0], input.b[0], input.a[3], input.b[3]);
1293 apply_nnf_selectors(NNF_SELECTORS::NON_NATIVE_FIELD_2);
1294 this->increment_num_gates();
1295
1296 //
1297 // Gate 5: NNF gate to check if hi_1 was computed correctly
1298 //
1299 // | a1 | b1 | r0 | lo_0 |
1300 // | a0 | b0 | a3 | b3 |
1301 // | a2 | b2 | r3 | hi_0 | <-- Gate 5: check hi_1
1302 // | a1 | b1 | r2 | hi_1 |
1303 //
1304 // Constraint: hi_1 = hi_0 + (a2 * b1 + a1 * b2) * 2^b + (a1 * b1) - r2
1305 // w'4 = w4 + (w1 * w'2 + w'1 * w2) * 2^b + (w'1 * w'2) - w'3
1306 //
1307 blocks.nnf.populate_wires(input.a[2], input.b[2], input.r[3], hi_0_idx);
1308 apply_nnf_selectors(NNF_SELECTORS::NON_NATIVE_FIELD_3);
1309 this->increment_num_gates();
1310
1311 //
1312 // Gate 6: NNF gate with no constraints (q_nnf=0, truly unconstrained)
1313 // Provides values a[1], b[1], r[2], hi_1 to Gate 5 via shifts (w'1, w'2, w'3, w'4)
1314 //
1315 blocks.nnf.populate_wires(input.a[1], input.b[1], input.r[2], hi_1_idx);
1316 apply_nnf_selectors(NNF_SELECTORS::NNF_NONE);
1317 this->increment_num_gates();
1318
1319 //
1320 // Gate 7: big_add_gate to validate hi_2
1321 //
1322 // hi_2 - hi_1 - lo_1 - q[2](p[1].2^b + p[0]) - q[3](p[0].2^b) = 0
1323 //
1324 create_big_add_gate(
1325 {
1326 input.q[2],
1327 input.q[3],
1328 lo_1_idx,
1329 hi_1_idx,
1330 -input.neg_modulus[1] * LIMB_SHIFT - input.neg_modulus[0],
1331 -input.neg_modulus[0] * LIMB_SHIFT,
1332 -1,
1333 -1,
1334 0,
1335 },
1336 /*include_next_gate_w_4*/ true);
1337
1338 //
1339 // Gate 8: big_add_gate to validate hi_3 (provides hi_2 in w_4 for gate 7)
1340 //
1341 // hi_3 - (hi_2 - q[0](p[3].2^b + p[2]) - q[1](p[2].2^b + p[1])).2^-2b = 0
1342 //
1343 create_big_add_gate({
1344 hi_3_idx,
1345 input.q[0],
1346 input.q[1],
1347 hi_2_idx,
1348 -1,
1349 input.neg_modulus[3] * LIMB_RSHIFT + input.neg_modulus[2] * LIMB_RSHIFT_2,
1350 input.neg_modulus[2] * LIMB_RSHIFT + input.neg_modulus[1] * LIMB_RSHIFT_2,
1351 LIMB_RSHIFT_2,
1352 0,
1353 });
1354
1355 return std::array<uint32_t, 2>{ lo_1_idx, hi_3_idx };
1356}
1357
1365{
1366 for (size_t i = 0; i < cached_partial_non_native_field_multiplications.size(); ++i) {
1367 auto& c = cached_partial_non_native_field_multiplications[i];
1368 for (size_t j = 0; j < c.a.size(); ++j) {
1369 c.a[j] = this->real_variable_index[c.a[j]];
1370 c.b[j] = this->real_variable_index[c.b[j]];
1371 }
1372 }
1373 cached_partial_non_native_field_multiplication::deduplicate(cached_partial_non_native_field_multiplications, this);
1374
1375 // iterate over the cached items and create constraints
1376 for (const auto& input : cached_partial_non_native_field_multiplications) {
1377
1378 blocks.nnf.populate_wires(input.a[1], input.b[1], this->zero_idx(), input.lo_0);
1379 apply_nnf_selectors(NNF_SELECTORS::NON_NATIVE_FIELD_1);
1380 this->increment_num_gates();
1381
1382 blocks.nnf.populate_wires(input.a[0], input.b[0], input.a[3], input.b[3]);
1383 apply_nnf_selectors(NNF_SELECTORS::NON_NATIVE_FIELD_2);
1384 this->increment_num_gates();
1385
1386 blocks.nnf.populate_wires(input.a[2], input.b[2], this->zero_idx(), input.hi_0);
1387 apply_nnf_selectors(NNF_SELECTORS::NON_NATIVE_FIELD_3);
1388 this->increment_num_gates();
1389
1390 blocks.nnf.populate_wires(input.a[1], input.b[1], this->zero_idx(), input.hi_1);
1391 apply_nnf_selectors(NNF_SELECTORS::NNF_NONE);
1392 this->increment_num_gates();
1393 }
1394}
1395
1402template <typename ExecutionTrace>
1405{
1407 this->get_variable(input.a[0]),
1408 this->get_variable(input.a[1]),
1409 this->get_variable(input.a[2]),
1410 this->get_variable(input.a[3]),
1411 };
1413 this->get_variable(input.b[0]),
1414 this->get_variable(input.b[1]),
1415 this->get_variable(input.b[2]),
1416 this->get_variable(input.b[3]),
1417 };
1418
1419 constexpr FF LIMB_SHIFT = uint256_t(1) << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS;
1420
1421 FF lo_0 = a[0] * b[0] + ((a[1] * b[0] + a[0] * b[1]) * LIMB_SHIFT);
1422 FF hi_0 = a[2] * b[0] + a[0] * b[2] + ((a[0] * b[3] + a[3] * b[0]) * LIMB_SHIFT);
1423 FF hi_1 = hi_0 + a[1] * b[1] + ((a[1] * b[2] + a[2] * b[1]) * LIMB_SHIFT);
1424
1425 const uint32_t lo_0_idx = this->add_variable(lo_0);
1426 const uint32_t hi_0_idx = this->add_variable(hi_0);
1427 const uint32_t hi_1_idx = this->add_variable(hi_1);
1428
1429 // Add witnesses into the multiplication cache (duplicates removed during circuit finalization)
1431 .a = input.a,
1432 .b = input.b,
1433 .lo_0 = lo_0_idx,
1434 .hi_0 = hi_0_idx,
1435 .hi_1 = hi_1_idx,
1436 };
1437 cached_partial_non_native_field_multiplications.emplace_back(cache_entry);
1438 return std::array<uint32_t, 2>{ lo_0_idx, hi_1_idx };
1439}
1440
1446template <typename ExecutionTrace>
1449{
1450 const uint32_t& x_0 = std::get<0>(limb0).first;
1451 const uint32_t& x_1 = std::get<0>(limb1).first;
1452 const uint32_t& x_2 = std::get<0>(limb2).first;
1453 const uint32_t& x_3 = std::get<0>(limb3).first;
1454 const uint32_t& x_p = std::get<0>(limbp);
1455
1456 const FF& x_mulconst0 = std::get<0>(limb0).second;
1457 const FF& x_mulconst1 = std::get<0>(limb1).second;
1458 const FF& x_mulconst2 = std::get<0>(limb2).second;
1459 const FF& x_mulconst3 = std::get<0>(limb3).second;
1460
1461 const uint32_t& y_0 = std::get<1>(limb0).first;
1462 const uint32_t& y_1 = std::get<1>(limb1).first;
1463 const uint32_t& y_2 = std::get<1>(limb2).first;
1464 const uint32_t& y_3 = std::get<1>(limb3).first;
1465 const uint32_t& y_p = std::get<1>(limbp);
1466
1467 const FF& y_mulconst0 = std::get<1>(limb0).second;
1468 const FF& y_mulconst1 = std::get<1>(limb1).second;
1469 const FF& y_mulconst2 = std::get<1>(limb2).second;
1470 const FF& y_mulconst3 = std::get<1>(limb3).second;
1471
1472 // constant additive terms
1473 const FF& addconst0 = std::get<2>(limb0);
1474 const FF& addconst1 = std::get<2>(limb1);
1475 const FF& addconst2 = std::get<2>(limb2);
1476 const FF& addconst3 = std::get<2>(limb3);
1477 const FF& addconstp = std::get<2>(limbp);
1478
1479 // get value of result limbs
1480 const FF z_0value = (this->get_variable(x_0) * x_mulconst0) + (this->get_variable(y_0) * y_mulconst0) + addconst0;
1481 const FF z_1value = (this->get_variable(x_1) * x_mulconst1) + (this->get_variable(y_1) * y_mulconst1) + addconst1;
1482 const FF z_2value = (this->get_variable(x_2) * x_mulconst2) + (this->get_variable(y_2) * y_mulconst2) + addconst2;
1483 const FF z_3value = (this->get_variable(x_3) * x_mulconst3) + (this->get_variable(y_3) * y_mulconst3) + addconst3;
1484 const FF z_pvalue = this->get_variable(x_p) + this->get_variable(y_p) + addconstp;
1485
1486 const uint32_t z_0 = this->add_variable(z_0value);
1487 const uint32_t z_1 = this->add_variable(z_1value);
1488 const uint32_t z_2 = this->add_variable(z_2value);
1489 const uint32_t z_3 = this->add_variable(z_3value);
1490 const uint32_t z_p = this->add_variable(z_pvalue);
1491
1513 auto& block = blocks.arithmetic;
1514 block.populate_wires(y_p, x_0, y_0, x_p);
1515 block.populate_wires(z_p, x_1, y_1, z_0);
1516 block.populate_wires(x_2, y_2, z_2, z_1);
1517 block.populate_wires(x_3, y_3, z_3, this->zero_idx());
1518
1519 // When q_arith == 3, w_4_shift is scaled by 2 (see ArithmeticRelation for details). Therefore, for consistency we
1520 // also scale each linear term by this factor of 2 so that the constraint is effectively:
1521 // (q_l * w_1) + (q_r * w_2) + (q_o * w_3) + (q_4 * w_4) + q_c + w_4_shift = 0
1522 const FF linear_term_scale_factor = 2;
1523 block.q_m().emplace_back(addconstp);
1524 block.q_1().emplace_back(0);
1525 block.q_2().emplace_back(-x_mulconst0 * linear_term_scale_factor);
1526 block.q_3().emplace_back(-y_mulconst0 * linear_term_scale_factor);
1527 block.q_4().emplace_back(0);
1528 block.q_5().emplace_back(0);
1529 block.q_c().emplace_back(-addconst0 * linear_term_scale_factor);
1530 block.set_gate_selector(3);
1531
1532 block.q_m().emplace_back(0);
1533 block.q_1().emplace_back(0);
1534 block.q_2().emplace_back(-x_mulconst1);
1535 block.q_3().emplace_back(-y_mulconst1);
1536 block.q_4().emplace_back(0);
1537 block.q_5().emplace_back(0);
1538 block.q_c().emplace_back(-addconst1);
1539 block.set_gate_selector(2);
1540
1541 block.q_m().emplace_back(0);
1542 block.q_1().emplace_back(-x_mulconst2);
1543 block.q_2().emplace_back(-y_mulconst2);
1544 block.q_3().emplace_back(1);
1545 block.q_4().emplace_back(0);
1546 block.q_5().emplace_back(0);
1547 block.q_c().emplace_back(-addconst2);
1548 block.set_gate_selector(1);
1549
1550 block.q_m().emplace_back(0);
1551 block.q_1().emplace_back(-x_mulconst3);
1552 block.q_2().emplace_back(-y_mulconst3);
1553 block.q_3().emplace_back(1);
1554 block.q_4().emplace_back(0);
1555 block.q_5().emplace_back(0);
1556 block.q_c().emplace_back(-addconst3);
1557 block.set_gate_selector(1);
1558
1559 check_selector_length_consistency();
1560
1561 this->increment_num_gates(4);
1563 z_0, z_1, z_2, z_3, z_p,
1564 };
1565}
1566
1572template <typename ExecutionTrace>
1575{
1576 const uint32_t& x_0 = std::get<0>(limb0).first;
1577 const uint32_t& x_1 = std::get<0>(limb1).first;
1578 const uint32_t& x_2 = std::get<0>(limb2).first;
1579 const uint32_t& x_3 = std::get<0>(limb3).first;
1580 const uint32_t& x_p = std::get<0>(limbp);
1581
1582 const FF& x_mulconst0 = std::get<0>(limb0).second;
1583 const FF& x_mulconst1 = std::get<0>(limb1).second;
1584 const FF& x_mulconst2 = std::get<0>(limb2).second;
1585 const FF& x_mulconst3 = std::get<0>(limb3).second;
1586
1587 const uint32_t& y_0 = std::get<1>(limb0).first;
1588 const uint32_t& y_1 = std::get<1>(limb1).first;
1589 const uint32_t& y_2 = std::get<1>(limb2).first;
1590 const uint32_t& y_3 = std::get<1>(limb3).first;
1591 const uint32_t& y_p = std::get<1>(limbp);
1592
1593 const FF& y_mulconst0 = std::get<1>(limb0).second;
1594 const FF& y_mulconst1 = std::get<1>(limb1).second;
1595 const FF& y_mulconst2 = std::get<1>(limb2).second;
1596 const FF& y_mulconst3 = std::get<1>(limb3).second;
1597
1598 // constant additive terms
1599 const FF& addconst0 = std::get<2>(limb0);
1600 const FF& addconst1 = std::get<2>(limb1);
1601 const FF& addconst2 = std::get<2>(limb2);
1602 const FF& addconst3 = std::get<2>(limb3);
1603 const FF& addconstp = std::get<2>(limbp);
1604
1605 // get value of result limbs
1606 const FF z_0value = (this->get_variable(x_0) * x_mulconst0) - (this->get_variable(y_0) * y_mulconst0) + addconst0;
1607 const FF z_1value = (this->get_variable(x_1) * x_mulconst1) - (this->get_variable(y_1) * y_mulconst1) + addconst1;
1608 const FF z_2value = (this->get_variable(x_2) * x_mulconst2) - (this->get_variable(y_2) * y_mulconst2) + addconst2;
1609 const FF z_3value = (this->get_variable(x_3) * x_mulconst3) - (this->get_variable(y_3) * y_mulconst3) + addconst3;
1610 const FF z_pvalue = this->get_variable(x_p) - this->get_variable(y_p) + addconstp;
1611
1612 const uint32_t z_0 = this->add_variable(z_0value);
1613 const uint32_t z_1 = this->add_variable(z_1value);
1614 const uint32_t z_2 = this->add_variable(z_2value);
1615 const uint32_t z_3 = this->add_variable(z_3value);
1616 const uint32_t z_p = this->add_variable(z_pvalue);
1617
1642 auto& block = blocks.arithmetic;
1643 block.populate_wires(y_p, x_0, y_0, z_p);
1644 block.populate_wires(x_p, x_1, y_1, z_0);
1645 block.populate_wires(x_2, y_2, z_2, z_1);
1646 block.populate_wires(x_3, y_3, z_3, this->zero_idx());
1647
1648 // When q_arith == 3, w_4_shift is scaled by 2 (see ArithmeticRelation for details). Therefore, for consistency we
1649 // also scale each linear term by this factor of 2 so that the constraint is effectively:
1650 // (q_l * w_1) + (q_r * w_2) + (q_o * w_3) + (q_4 * w_4) + q_c + w_4_shift = 0
1651 const FF linear_term_scale_factor = 2;
1652 block.q_m().emplace_back(-addconstp);
1653 block.q_1().emplace_back(0);
1654 block.q_2().emplace_back(-x_mulconst0 * linear_term_scale_factor);
1655 block.q_3().emplace_back(y_mulconst0 * linear_term_scale_factor);
1656 block.q_4().emplace_back(0);
1657 block.q_5().emplace_back(0);
1658 block.q_c().emplace_back(-addconst0 * linear_term_scale_factor);
1659 block.set_gate_selector(3);
1660
1661 block.q_m().emplace_back(0);
1662 block.q_1().emplace_back(0);
1663 block.q_2().emplace_back(-x_mulconst1);
1664 block.q_3().emplace_back(y_mulconst1);
1665 block.q_4().emplace_back(0);
1666 block.q_5().emplace_back(0);
1667 block.q_c().emplace_back(-addconst1);
1668 block.set_gate_selector(2);
1669
1670 block.q_m().emplace_back(0);
1671 block.q_1().emplace_back(-x_mulconst2);
1672 block.q_2().emplace_back(y_mulconst2);
1673 block.q_3().emplace_back(1);
1674 block.q_4().emplace_back(0);
1675 block.q_5().emplace_back(0);
1676 block.q_c().emplace_back(-addconst2);
1677 block.set_gate_selector(1);
1678
1679 block.q_m().emplace_back(0);
1680 block.q_1().emplace_back(-x_mulconst3);
1681 block.q_2().emplace_back(y_mulconst3);
1682 block.q_3().emplace_back(1);
1683 block.q_4().emplace_back(0);
1684 block.q_5().emplace_back(0);
1685 block.q_c().emplace_back(-addconst3);
1686 block.set_gate_selector(1);
1687
1688 check_selector_length_consistency();
1689
1690 this->increment_num_gates(4);
1692 z_0, z_1, z_2, z_3, z_p,
1693 };
1694}
1695
1705template <typename ExecutionTrace>
1707{
1708 return this->rom_ram_logic.create_ROM_array(array_size);
1709}
1710
1720template <typename ExecutionTrace>
1722{
1723 return this->rom_ram_logic.create_RAM_array(array_size);
1724}
1725
1733template <typename ExecutionTrace>
1735 const size_t index_value,
1736 const uint32_t value_witness)
1737{
1738 this->rom_ram_logic.init_RAM_element(this, ram_id, index_value, value_witness);
1739}
1740
1741template <typename ExecutionTrace>
1742uint32_t UltraCircuitBuilder_<ExecutionTrace>::read_RAM_array(const size_t ram_id, const uint32_t index_witness)
1743{
1744 return this->rom_ram_logic.read_RAM_array(this, ram_id, index_witness);
1745}
1746
1747template <typename ExecutionTrace>
1749 const uint32_t index_witness,
1750 const uint32_t value_witness)
1751{
1752 this->rom_ram_logic.write_RAM_array(this, ram_id, index_witness, value_witness);
1753}
1754
1770template <typename ExecutionTrace>
1772 const size_t index_value,
1773 const uint32_t value_witness)
1774{
1775 this->rom_ram_logic.set_ROM_element(this, rom_id, index_value, value_witness);
1776}
1777
1785template <typename ExecutionTrace>
1787 const size_t index_value,
1788 const std::array<uint32_t, 2>& value_witnesses)
1789{
1790 this->rom_ram_logic.set_ROM_element_pair(this, rom_id, index_value, value_witnesses);
1791}
1792
1800template <typename ExecutionTrace>
1801uint32_t UltraCircuitBuilder_<ExecutionTrace>::read_ROM_array(const size_t rom_id, const uint32_t index_witness)
1802{
1803 return this->rom_ram_logic.read_ROM_array(this, rom_id, index_witness);
1804}
1805
1813template <typename ExecutionTrace>
1814std::array<uint32_t, 2> UltraCircuitBuilder_<ExecutionTrace>::read_ROM_array_pair(const size_t rom_id,
1815 const uint32_t index_witness)
1816{
1817 return this->rom_ram_logic.read_ROM_array_pair(this, rom_id, index_witness);
1818}
1819
1823template <typename FF>
1825{
1826 auto& block = this->blocks.poseidon2_external;
1827 block.populate_wires(in.a, in.b, in.c, in.d);
1828 block.q_m().emplace_back(0);
1832 block.q_c().emplace_back(0);
1834 block.q_5().emplace_back(0);
1835 block.set_gate_selector(GateKind::Poseidon2Ext, 1);
1836 this->check_selector_length_consistency();
1837 this->increment_num_gates();
1838}
1839
1844template <typename FF>
1846{
1847 if constexpr (requires { this->blocks.poseidon2_internal; }) {
1848 auto& block = this->blocks.poseidon2_internal;
1849 block.populate_wires(in.a, in.b, in.c, in.d);
1850 block.q_m().emplace_back(0);
1852 block.q_2().emplace_back(0);
1853 block.q_3().emplace_back(0);
1854 block.q_c().emplace_back(0);
1855 block.q_4().emplace_back(0);
1856 block.q_5().emplace_back(0);
1857 block.set_gate_selector(1);
1858 this->check_selector_length_consistency();
1859 this->increment_num_gates();
1860 } else {
1861 throw_or_abort("create_poseidon2_internal_gate is Ultra-only (Mega uses the compressed block)");
1862 }
1863}
1864
1871template <typename ExecutionTrace> msgpack::sbuffer UltraCircuitBuilder_<ExecutionTrace>::export_circuit()
1872{
1873 // You should not name `zero` by yourself
1874 // but it will be rewritten anyway
1875 auto first_zero_idx = this->get_first_variable_in_class(this->zero_idx());
1876 if (!this->variable_names.contains(first_zero_idx)) {
1877 this->set_variable_name(this->zero_idx(), "zero");
1878 } else {
1879 this->variable_names[first_zero_idx] = "zero";
1880 }
1881 using base = CircuitBuilderBase<FF>;
1883
1884 std::array<uint64_t, 4> modulus = {
1885 FF::Params::modulus_0, FF::Params::modulus_1, FF::Params::modulus_2, FF::Params::modulus_3
1886 };
1887 std::stringstream buf;
1888 buf << std::hex << std::setfill('0') << std::setw(16) << modulus[3] << std::setw(16) << modulus[2] << std::setw(16)
1889 << modulus[1] << std::setw(16) << modulus[0];
1890
1891 cir.modulus = buf.str();
1892
1893 for (uint32_t i = 0; i < this->num_public_inputs(); i++) {
1894 cir.public_inps.push_back(this->real_variable_index[this->public_inputs()[i]]);
1895 }
1896
1897 for (auto& tup : base::variable_names) {
1898 cir.vars_of_interest.insert({ this->real_variable_index[tup.first], tup.second });
1899 }
1900
1901 for (const auto& var : this->get_variables()) {
1902 cir.variables.push_back(var);
1903 }
1904
1905 FF curve_b;
1906 if constexpr (FF::modulus == bb::fq::modulus) {
1907 curve_b = bb::g1::curve_b;
1908 } else if constexpr (FF::modulus == grumpkin::fq::modulus) {
1909 curve_b = grumpkin::g1::curve_b;
1910 } else {
1911 curve_b = 0;
1912 }
1913
1914 for (auto& block : blocks.get()) {
1915 std::vector<std::vector<FF>> block_selectors;
1917 for (size_t idx = 0; idx < block.size(); ++idx) {
1918 std::vector<FF> tmp_sel = { block.q_m()[idx],
1919 block.q_1()[idx],
1920 block.q_2()[idx],
1921 block.q_3()[idx],
1922 block.q_4()[idx],
1923 block.q_c()[idx],
1928 read_gate_selector(block, GateKind::Nnf, idx),
1930 curve_b };
1931
1932 std::vector<uint32_t> tmp_w = {
1933 this->real_variable_index[block.w_l()[idx]],
1934 this->real_variable_index[block.w_r()[idx]],
1935 this->real_variable_index[block.w_o()[idx]],
1936 this->real_variable_index[block.w_4()[idx]],
1937 };
1938
1939 if (idx < block.size() - 1) {
1940 tmp_w.push_back(block.w_l()[idx + 1]);
1941 tmp_w.push_back(block.w_r()[idx + 1]);
1942 tmp_w.push_back(block.w_o()[idx + 1]);
1943 tmp_w.push_back(block.w_4()[idx + 1]);
1944 } else {
1945 tmp_w.push_back(0);
1946 tmp_w.push_back(0);
1947 tmp_w.push_back(0);
1948 tmp_w.push_back(0);
1949 }
1950
1951 block_selectors.push_back(tmp_sel);
1952 block_wires.push_back(tmp_w);
1953 }
1954 cir.selectors.push_back(block_selectors);
1955 cir.wires.push_back(block_wires);
1956 }
1957
1958 cir.real_variable_index = this->real_variable_index;
1959
1960 for (const auto& table : this->lookup_tables) {
1961 const FF table_index(table.table_index);
1962 info("Table no: ", table.table_index);
1963 std::vector<std::vector<FF>> tmp_table;
1964 for (size_t i = 0; i < table.size(); ++i) {
1965 tmp_table.push_back({ table.column_1[i], table.column_2[i], table.column_3[i] });
1966 }
1967 cir.lookup_tables.push_back(tmp_table);
1968 }
1969
1970 cir.real_variable_tags = this->real_variable_tags;
1971
1972 for (const auto& list : range_lists) {
1973 cir.range_tags[list.second.range_tag] = list.first;
1974 }
1975
1976 for (auto& rom_table : this->rom_ram_logic.rom_arrays) {
1977 std::sort(rom_table.records.begin(), rom_table.records.end());
1978
1980 table.reserve(rom_table.records.size());
1981 for (const auto& rom_entry : rom_table.records) {
1982 table.push_back({
1983 this->real_variable_index[rom_entry.index_witness],
1984 this->real_variable_index[rom_entry.value_column1_witness],
1985 this->real_variable_index[rom_entry.value_column2_witness],
1986 });
1987 }
1988 cir.rom_records.push_back(table);
1989 cir.rom_states.push_back(rom_table.state);
1990 }
1991
1992 for (auto& ram_table : this->rom_ram_logic.ram_arrays) {
1993 std::sort(ram_table.records.begin(), ram_table.records.end());
1994
1996 table.reserve(ram_table.records.size());
1997 for (const auto& ram_entry : ram_table.records) {
1998 table.push_back({ this->real_variable_index[ram_entry.index_witness],
1999 this->real_variable_index[ram_entry.value_witness],
2000 this->real_variable_index[ram_entry.timestamp_witness],
2001 ram_entry.access_type });
2002 }
2003 cir.ram_records.push_back(table);
2004 cir.ram_states.push_back(ram_table.state);
2005 }
2006
2007 cir.circuit_finalized = this->circuit_finalized;
2008
2009 msgpack::sbuffer buffer;
2010 msgpack::pack(buffer, cir);
2011 return buffer;
2012}
2013
2016
2017} // 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:264
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 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....
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....
void create_ecc_add_gate(const ecc_add_gate_ &in)
Create an elliptic curve addition gate.
plookup::BasicTable * register_basic_lookup_table(plookup::BasicTable &&table)
Register a BasicTable with the builder, assigning it a unique table_index.
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_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.
void create_lookup_gate(uint32_t key_idx, uint32_t val1_idx, uint32_t val2_idx, plookup::BasicTable &table, const plookup::BasicTable::LookupEntry &entry, FF column_1_step_size=0, FF column_2_step_size=0, FF column_3_step_size=0)
Create a single plookup lookup gate.
static constexpr Fq curve_b
Definition group.hpp:53
constexpr uint64_t get_msb() const
Container for lookup accumulator values and table reads.
Definition types.hpp:360
std::vector< BasicTable::LookupEntry > lookup_entries
Definition types.hpp:366
#define info(...)
Definition log.hpp:93
FF a
FF b
std::unique_ptr< uint8_t[]> buffer
Definition engine.cpp:60
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:155
FF read_gate_selector(const ExecutionTraceBlock< FF, NUM_WIRES > &block, GateKind kind, size_t idx)
Gate-selector value at (block, idx) for kind, returning zero if block does not own this kind....
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
Definition types.hpp:289
A basic table from which we can perform lookups (for example, an xor table)
Definition types.hpp:288
std::vector< LookupEntry > lookup_gates
Definition types.hpp:324
size_t size() const
Definition types.hpp:335
std::vector< bb::fr > column_3
Definition types.hpp:323
std::vector< bb::fr > column_2
Definition types.hpp:322
std::vector< bb::fr > column_1
Definition types.hpp:321
void throw_or_abort(std::string const &err)