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 std::unordered_set<size_t> table_indices;
55 for (const auto& table : lookup_tables) {
56 BB_ASSERT_GT(table.table_index, 0U, "Lookup table indices must be positive");
57 BB_ASSERT(table_indices.insert(table.table_index).second,
58 "Lookup table indices must be unique within a circuit");
59 }
60
61 process_non_native_field_multiplications();
62#ifndef ULTRA_FUZZ
63 this->rom_ram_logic.process_ROM_arrays(this);
64 this->rom_ram_logic.process_RAM_arrays(this);
65 process_range_lists();
66#endif
67 populate_public_inputs_block();
68 this->circuit_finalized = true;
69 } else {
70 // Gates added after first call to finalize will not be processed since finalization is only performed once
71 info("WARNING: Redundant call to finalize_circuit(). Is this intentional?");
72 }
73}
74
79{
80 BB_BENCH_NAME("populate_public_inputs_block");
81
82 // Update the public inputs block
83 for (const auto& idx : this->public_inputs()) {
84 // first two wires get a copy of the public inputs
85 blocks.pub_inputs.append_gate({ .wires = { idx, idx, this->zero_idx(), this->zero_idx() } });
86 }
87}
88
97template <typename ExecutionTrace> void UltraCircuitBuilder_<ExecutionTrace>::create_add_gate(const add_triple_<FF>& in)
98{
99 // Delegate to create_big_add_gate with 4th wire set to zero
100 create_big_add_gate({ .a = in.a,
101 .b = in.b,
102 .c = in.c,
103 .d = this->zero_idx(),
104 .a_scaling = in.a_scaling,
105 .b_scaling = in.b_scaling,
106 .c_scaling = in.c_scaling,
107 .d_scaling = 0,
108 .const_scaling = in.const_scaling });
109}
110
119template <typename ExecutionTrace>
121 const bool include_next_gate_w_4)
122{
123 this->assert_valid_variables({ in.a, in.b, in.c, in.d });
124 // If include_next_gate_w_4 is true then we set q_arith = 2. In this case, the linear term in the ArithmeticRelation
125 // is scaled by a factor of 2. We compensate here by scaling the quadratic term by 2 to achieve the constraint:
126 // 2 * [q_m * w_1 * w_2 + \sum_{i=1..4} q_i * w_i + q_c + w_4_shift] = 0
127 const FF mul_scaling = include_next_gate_w_4 ? in.mul_scaling * FF(2) : in.mul_scaling;
128 blocks.arithmetic.append_gate({ .wires = { in.a, in.b, in.c, in.d },
129 .q_m = mul_scaling,
130 .q_c = in.const_scaling,
131 .q_1 = in.a_scaling,
132 .q_2 = in.b_scaling,
133 .q_3 = in.c_scaling,
134 .q_4 = in.d_scaling,
135 .gate_kind = GateKind::Arith,
136 .gate_value = include_next_gate_w_4 ? 2 : 1 });
137 this->increment_num_gates();
138}
139
148template <typename ExecutionTrace>
150 const bool include_next_gate_w_4)
151{
152 this->assert_valid_variables({ in.a, in.b, in.c, in.d });
153 blocks.arithmetic.append_gate({ .wires = { in.a, in.b, in.c, in.d },
154 .q_c = in.const_scaling,
155 .q_1 = in.a_scaling,
156 .q_2 = in.b_scaling,
157 .q_3 = in.c_scaling,
158 .q_4 = in.d_scaling,
159 .gate_kind = GateKind::Arith,
160 .gate_value = include_next_gate_w_4 ? 2 : 1 });
161 this->increment_num_gates();
162}
163
174template <typename ExecutionTrace>
176{
177 this->assert_valid_variables({ in.a, in.b, in.c, in.d });
178
179 // BILINEAR: q_m·a·b + q_5·a·c + q_l·a + q_r·b + q_o·c + q_4·d + q_c = 0 (two products sharing wire a,
180 // on the pairs (a, b) and (a, c) via q_m and q_5; q_l..q_4 the per-wire linear coefficients,
181 // q_c the constant; wire d appears only in its linear term).
182 // BATCHED_EQ: q_l·a + q_r·b + q_c = 0 and q_o·c + q_4·d + q_m = 0 (q_m repurposed as the 2nd
183 // constant; q_5 unused). Selectors are written raw; only the q_bilinear_batched_eq gate
184 // selector value distinguishes the two modes.
185 {
186 auto& block_for_row = blocks.arithmetic;
187 GateRowT row{};
188 row.wires = { in.a, in.b, in.c, in.d };
189 row.q_m = in.q_m;
190 row.q_c = in.q_c;
191 row.q_1 = in.q_l;
192 row.q_2 = in.q_r;
193 row.q_3 = in.q_o;
194 row.q_4 = in.q_4;
195 row.q_5 = in.mode == BilinearBatchedEqMode::Bilinear ? in.q_5 : FF(0);
196 row.gate_kind = GateKind::BilinearBatchedEq;
197 row.gate_value = in.mode == BilinearBatchedEqMode::Bilinear ? 1 : 2;
198 block_for_row.append_gate(row);
199 }
200 this->increment_num_gates();
201}
202
208template <typename ExecutionTrace>
210{
211 this->assert_valid_variables({ variable_index });
212
213 blocks.arithmetic.append_gate({ .wires = { variable_index, variable_index, this->zero_idx(), this->zero_idx() },
214 .q_m = 1,
215 .q_1 = -1,
216 .gate_kind = GateKind::Arith,
217 .gate_value = 1 });
218 this->increment_num_gates();
219}
220
227template <typename ExecutionTrace>
229{
230 this->assert_valid_variables({ in.a, in.b, in.c });
231
232 blocks.arithmetic.append_gate({ .wires = { in.a, in.b, in.c, this->zero_idx() },
233 .q_m = in.q_m,
234 .q_c = in.q_c,
235 .q_1 = in.q_l,
236 .q_2 = in.q_r,
237 .q_3 = in.q_o,
238 .gate_kind = GateKind::Arith,
239 .gate_value = 1 });
240 this->increment_num_gates();
241}
242
259template <typename ExecutionTrace>
261{
262 this->assert_valid_variables({ in.x1, in.x2, in.x3, in.y1, in.y2, in.y3 });
263
264 auto& block = blocks.elliptic;
265
266 // Convert bool to field element for the relation: +1 for addition, -1 for subtraction
267 // The elliptic curve relation assumes q_sign² = 1 (see elliptic_relation.hpp)
268 const FF q_sign = in.is_addition ? FF(1) : FF(-1);
269
270 // Determine whether we can fuse this addition operation into the previous gate in the block
271 bool can_fuse_into_previous_gate =
272 block.size() > 0 && /* a previous gate exists in the block */
273 block.w_r()[block.size() - 1] == in.x1 && /* output x coord of previous gate is input of this one */
274 block.w_o()[block.size() - 1] == in.y1; /* output y coord of previous gate is input of this one */
275
276 if (can_fuse_into_previous_gate) {
277 block.q_1().set(block.size() - 1, q_sign); // set q_sign of previous gate
278 block.gate_selector_for(GateKind::Elliptic).set(block.size() - 1, 1); // set q_ecc of previous gate to 1
279 } else {
280 {
281 auto& block_for_row = block;
282 GateRowT row{};
283 row.wires = { this->zero_idx(), in.x1, in.y1, this->zero_idx() };
284 row.q_1 = q_sign;
285 row.gate_kind = GateKind::Elliptic;
286 row.gate_value = 1;
287 block_for_row.append_gate(row);
288 }
289 this->increment_num_gates();
291 // Create the unconstrained gate with the output of the doubling to be read into by the previous gate via shifts
292 create_unconstrained_gate(block, in.x2, in.x3, in.y3, in.y2);
293}
311template <typename ExecutionTrace>
313{
314 this->assert_valid_variables({ in.x1, in.x3, in.y1, in.y3 });
315
316 auto& block = blocks.elliptic;
317
318 // Determine whether we can fuse this doubling operation into the previous gate in the block
319 bool can_fuse_into_previous_gate =
320 block.size() > 0 && /* a previous gate exists in the block */
321 block.w_r()[block.size() - 1] == in.x1 && /* output x coord of previous gate is input of this one */
322 block.w_o()[block.size() - 1] == in.y1; /* output y coord of previous gate is input of this one */
323
324 // If possible, update the previous gate to be the first gate in the pair, otherwise create a new gate
325 if (can_fuse_into_previous_gate) {
326 block.gate_selector_for(GateKind::Elliptic).set(block.size() - 1, 1); // set q_ecc of previous gate to 1
327 block.q_m().set(block.size() - 1, 1); // set q_m (q_is_double) of previous gate to 1
328 } else {
329 {
330 auto& block_for_row = block;
331 GateRowT row{};
332 row.wires = { this->zero_idx(), in.x1, in.y1, this->zero_idx() };
333 row.q_m = 1;
334 row.gate_kind = GateKind::Elliptic;
335 row.gate_value = 1;
336 block_for_row.append_gate(row);
337 }
338 this->increment_num_gates();
339 }
340 // Create the unconstrained gate with the output of the doubling to be read into by the previous gate via shifts
341 create_unconstrained_gate(block, this->zero_idx(), in.x3, in.y3, this->zero_idx());
342}
343
350template <typename ExecutionTrace>
351void UltraCircuitBuilder_<ExecutionTrace>::fix_witness(const uint32_t witness_index, const FF& witness_value)
352{
353 this->assert_valid_variables({ witness_index });
354
355 // Mark as intentionally single-gate for boomerang detection
356 update_used_witnesses(witness_index);
357
358 blocks.arithmetic.append_gate({ .wires = { witness_index, this->zero_idx(), this->zero_idx(), this->zero_idx() },
359 .q_c = -witness_value,
360 .q_1 = 1,
361 .gate_kind = GateKind::Arith,
362 .gate_value = 1 });
363 this->increment_num_gates();
364}
365
366template <typename ExecutionTrace>
368{
369 if (constant_variable_indices.contains(variable)) {
370 return constant_variable_indices.at(variable);
371 } else {
372 uint32_t variable_index = this->add_variable(variable);
373 fix_witness(variable_index, variable);
374 constant_variable_indices.insert({ variable, variable_index });
375 return variable_index;
376 }
377}
378
387template <typename ExecutionTrace>
389{
390 for (plookup::BasicTable& table : lookup_tables) {
391 if (table.id == id) {
392 return table;
393 }
394 }
395 // Table doesn't exist! So try to create it. Indices start at 1 so that the relation batched term always contains at
396 // least beta cubed.
397 lookup_tables.emplace_back(plookup::create_basic_table(id, lookup_tables.size() + 1));
398 return lookup_tables.back();
399}
400
402template <typename ExecutionTrace>
404{
405 // Indices start at 1; see get_table() for the reason.
406 table.table_index = lookup_tables.size() + 1;
407 BB_ASSERT_GT(table.table_index, 0U, "Table index must be greater than 0");
408
409 lookup_tables.emplace_back(std::move(table));
410 return &lookup_tables.back();
411}
412
414template <typename ExecutionTrace>
416 const uint32_t val1_idx,
417 const uint32_t val2_idx,
418 plookup::BasicTable& table,
420 const FF column_1_step_size,
421 const FF column_2_step_size,
422 const FF column_3_step_size)
423{
424 this->assert_valid_variables({ key_idx, val1_idx, val2_idx });
425
426 table.lookup_gates.emplace_back(entry);
428 {
429 auto& block_for_row = blocks.lookup;
430 GateRowT row{};
431 row.wires = { key_idx, val1_idx, val2_idx, this->zero_idx() };
432 row.gate_kind = GateKind::Lookup;
433 row.gate_value = 1;
434 row.q_m = column_2_step_size;
435 row.q_c = column_3_step_size;
436 row.q_2 = column_1_step_size;
437 row.q_3 = FF(table.table_index);
438 block_for_row.append_gate(row);
439 }
440 this->increment_num_gates();
441}
442
470template <typename ExecutionTrace>
472 const plookup::MultiTableId& id,
473 const plookup::ReadData<FF>& read_values,
474 const uint32_t key_a_index,
475 std::optional<uint32_t> key_b_index)
476{
478
479 const auto& multi_table = plookup::get_multitable(id);
480 const size_t num_lookups = read_values[ColumnIdx::C1].size();
482
483 for (size_t i = 0; i < num_lookups; ++i) {
484 const bool is_first_lookup = (i == 0);
485 const bool is_last_lookup = (i == num_lookups - 1);
486
487 // Get basic lookup table; construct and add to builder.lookup_tables if not already present
488 plookup::BasicTable& table = get_table(multi_table.basic_table_ids[i]);
489
490 // Create witness variables: first lookup reuses user's input indices, subsequent create new variables
491 const auto first_idx = is_first_lookup ? key_a_index : this->add_variable(read_values[ColumnIdx::C1][i]);
492 const auto second_idx = (is_first_lookup && key_b_index.has_value())
493 ? *key_b_index
494 : this->add_variable(read_values[ColumnIdx::C2][i]);
495 const auto third_idx = this->add_variable(read_values[ColumnIdx::C3][i]);
496
497 read_data[ColumnIdx::C1].push_back(first_idx);
498 read_data[ColumnIdx::C2].push_back(second_idx);
499 read_data[ColumnIdx::C3].push_back(third_idx);
500
501 // Step size coefficients: zero for last lookup (no next accumulator), negative step sizes otherwise
502 const FF col1_step = is_last_lookup ? FF(0) : -multi_table.column_1_step_sizes[i + 1];
503 const FF col2_step = is_last_lookup ? FF(0) : -multi_table.column_2_step_sizes[i + 1];
504 const FF col3_step = is_last_lookup ? FF(0) : -multi_table.column_3_step_sizes[i + 1];
505
506 create_lookup_gate(
507 first_idx, second_idx, third_idx, table, read_values.lookup_entries[i], col1_step, col2_step, col3_step);
508 }
509 return read_data;
510}
511
515template <typename ExecutionTrace>
517 const uint64_t target_range)
518{
520 const auto range_tag = get_new_tag();
521 const auto tau_tag = get_new_tag();
522 set_tau_transposition(range_tag, tau_tag);
523 result.target_range = target_range;
524 result.range_tag = range_tag;
525 result.tau_tag = tau_tag;
526
527 uint64_t num_multiples_of_three = (target_range / DEFAULT_PLOOKUP_RANGE_STEP_SIZE);
528 // allocate the minimum number of variable indices required for the range constraint. this function is only called
529 // when we are creating a range constraint on a witness index, which is responsible for the extra + 1. (note that
530 // the below loop goes from 0 to `num_multiples_of_three` inclusive.)
531 result.variable_indices.reserve(static_cast<uint32_t>(num_multiples_of_three + 3));
532 for (uint64_t i = 0; i <= num_multiples_of_three; ++i) {
533 const uint32_t index = this->add_variable(fr(i * DEFAULT_PLOOKUP_RANGE_STEP_SIZE));
534 result.variable_indices.emplace_back(index);
535 assign_tag(index, result.range_tag);
536 }
537 // `target_range` may not be divisible by 3, so we explicitly add it also.
538 {
539 const uint32_t index = this->add_variable(fr(target_range));
540 result.variable_indices.emplace_back(index);
541 assign_tag(index, result.range_tag);
542 }
543 // Need this because these variables will not appear in the witness otherwise
544 create_unconstrained_gates(result.variable_indices);
545
546 return result;
547}
548
549template <typename ExecutionTrace>
551 const uint32_t variable_index, const uint64_t num_bits, const uint64_t target_range_bitnum, std::string_view msg)
552{
553 this->assert_valid_variables({ variable_index });
554 // make sure `num_bits` satisfies the correct bounds
555 BB_ASSERT_GT(num_bits, 0U);
556 BB_ASSERT_GTE(MAX_NUM_BITS_RANGE_CONSTRAINT, num_bits);
558 uint256_t val = (uint256_t)(this->get_variable(variable_index));
559
560 // If the value is out of range, set the CircuitBuilder error to the given msg.
561 if (val.get_msb() >= num_bits && !this->failed()) {
562 this->failure(std::string(msg));
563 }
564
565 // compute limb structure
566 const uint64_t sublimb_mask = (1ULL << target_range_bitnum) - 1;
568 std::vector<uint64_t> sublimbs;
569 std::vector<uint32_t> sublimb_indices;
570
571 const bool has_remainder_bits = (num_bits % target_range_bitnum != 0);
572 const uint64_t num_limbs = (num_bits / target_range_bitnum) + has_remainder_bits;
573 const uint64_t last_limb_size = num_bits - ((num_bits / target_range_bitnum) * target_range_bitnum);
574 const uint64_t last_limb_range = ((uint64_t)1 << last_limb_size) - 1;
575
576 // extract limbs from the value
577 uint256_t accumulator = val;
578 for (size_t i = 0; i < num_limbs; ++i) {
579 sublimbs.push_back(accumulator.data[0] & sublimb_mask);
580 accumulator = accumulator >> target_range_bitnum;
581 }
582 // set the correct range constraint on each limb. note that when there are remainder bits, the last limb must be
583 // constrained to a smaller range.
584 const size_t num_full_limbs = has_remainder_bits ? sublimbs.size() - 1 : sublimbs.size();
585 for (size_t i = 0; i < num_full_limbs; ++i) {
586 const auto limb_idx = this->add_variable(bb::fr(sublimbs[i]));
587 sublimb_indices.emplace_back(limb_idx);
588 create_small_range_constraint(limb_idx, sublimb_mask);
589 }
590 if (has_remainder_bits) {
591 const auto limb_idx = this->add_variable(bb::fr(sublimbs.back()));
592 sublimb_indices.emplace_back(limb_idx);
593 create_small_range_constraint(limb_idx, last_limb_range);
594 }
595
596 // Prove that the limbs reconstruct the original value by processing limbs in groups of 3.
597 // We constrain: value = sum_{j=0}^{num_limbs-1} limb[j] * 2^(j * target_range_bitnum)
598 //
599 // Each iteration subtracts 3 limbs' contributions from an accumulator (starting at `val`),
600 // and constrains that the accumulator updates correctly via an arithmetic gate.
601 const uint64_t num_limb_triples = (num_limbs / 3) + ((num_limbs % 3) != 0);
602 // `leftovers` is the number of real limbs in the final triple (1, 2, or 3).
603 const uint64_t leftovers = (num_limbs % 3) == 0 ? 3 : (num_limbs % 3);
605 accumulator = val;
606 uint32_t accumulator_idx = variable_index;
607 // loop goes from `i = 0` to `num_limb_triples`, but some special case must be taken for the last triple (`i ==
608 // num_limb_triples - 1`), hence some conditional logic.
609 for (size_t i = 0; i < num_limb_triples; ++i) {
610 // `real_limbs` which limb positions in this triple contain actual limbs vs zero-padding.
611 // When `i == num_limb_triples - 1`, some positions may be unused if `num_limbs` isn't divisible by 3.
612 const bool real_limbs[3]{
613 !(i == (num_limb_triples - 1) && (leftovers < 1)),
614 !(i == (num_limb_triples - 1) && (leftovers < 2)),
615 !(i == (num_limb_triples - 1) && (leftovers < 3)),
616 };
617
618 // The witness values of the 3 limbs in this triple (0 for padding positions).
619 const uint64_t round_sublimbs[3]{
620 real_limbs[0] ? sublimbs[3 * i] : 0,
621 real_limbs[1] ? sublimbs[3 * i + 1] : 0,
622 real_limbs[2] ? sublimbs[3 * i + 2] : 0,
623 };
624 // The witnesss indices of the current 3 limbs (zero_idx for padding positions).
625 const uint32_t new_limbs[3]{
626 real_limbs[0] ? sublimb_indices[3 * i] : this->zero_idx(),
627 real_limbs[1] ? sublimb_indices[3 * i + 1] : this->zero_idx(),
628 real_limbs[2] ? sublimb_indices[3 * i + 2] : this->zero_idx(),
629 };
630 // Bit-shifts for each limb: limb[3*i+k] contributes at bit position (3*i+k) * target_range_bitnum.
631 const uint64_t shifts[3]{
632 target_range_bitnum * (3 * i),
633 target_range_bitnum * (3 * i + 1),
634 target_range_bitnum * (3 * i + 2),
635 };
636 // Compute the new accumulator after subtracting this triple's contribution.
637 // After the final iteration, accumulator should be 0.
638 uint256_t new_accumulator = accumulator - (uint256_t(round_sublimbs[0]) << shifts[0]) -
639 (uint256_t(round_sublimbs[1]) << shifts[1]) -
640 (uint256_t(round_sublimbs[2]) << shifts[2]);
641
642 // This `big_add_gate` has differing behavior depending on whether or not `i == num_limb_triples - 1`.
643 // If `i != num_limb_triples - 1`, then the constraint will be limb[0]*2^shift[0] + limb[1]*2^shift[1] +
644 // limb[2]*2^shift[2] - acc = new_accumulator (the last argument to `create_big_add_gate` is `true`, means the
645 // sum is w_4-shift, which will be the witness corresponding to what is currently `new_accumulator`.).
646 // If `i == num_limb_triples - 1`, then the last argument to `create_big_add_gate` is false, so the constraint
647 // is limb[0]*2^shift[0] + limb[1]*2^shift[1] + limb[2]*2^shift[2] - acc = 0.
648 //
649 // N.B. When `num_bits` is small, we only have remainder bits. This last constraint, checking the correctness of
650 // the limb-decomposition, ensures that the variable is not orphaned. (See the warning in
651 // `create_small_range_constraint`.)
652 create_big_add_gate(
653 {
654 new_limbs[0],
655 new_limbs[1],
656 new_limbs[2],
657 accumulator_idx,
658 uint256_t(1) << shifts[0],
659 uint256_t(1) << shifts[1],
660 uint256_t(1) << shifts[2],
661 -1,
662 0,
663 },
664 (i != num_limb_triples - 1));
665 if (i != num_limb_triples - 1) {
666 accumulator_idx = this->add_variable(fr(new_accumulator));
667 accumulator = new_accumulator;
668 }
669 }
670 return sublimb_indices;
671}
672
673template <typename ExecutionTrace>
675 const uint64_t target_range,
676 std::string_view msg)
677{
678 // make sure `target_range` is not too big.
679 BB_ASSERT_GTE(MAX_SMALL_RANGE_CONSTRAINT_VAL, target_range);
680 const bool is_out_of_range = (uint256_t(this->get_variable(variable_index)).data[0] > target_range);
681 if (is_out_of_range && !this->failed()) {
682 this->failure(std::string(msg));
684 if (range_lists.count(target_range) == 0) {
685 range_lists.insert({ target_range, create_range_list(target_range) });
686 }
687 // The tag of `variable_index` is `DEFAULT_TAG` if it has never been range-constrained and a non-trivial value
688 // otherwise.
689 const auto existing_tag = this->real_variable_tags[this->real_variable_index[variable_index]];
690 auto& list = range_lists[target_range];
691
692 // If the variable's tag matches the target range list's tag, do nothing; the variable has _already_ been
693 // constrained to this exact range (i.e., `create_new_range_constraint(variable_index, target_range)` has already
694 // been called).
695 if (existing_tag == list.range_tag) {
696 return;
697 }
698 // If the variable is 'untagged' (i.e., it has the dummy tag), assign it the appropriate tag, which amounts to
699 // setting the range-constraint.
700 if (existing_tag == DEFAULT_TAG) {
701 assign_tag(variable_index, list.range_tag);
702 list.variable_indices.emplace_back(variable_index);
703 return;
704 }
705 // Otherwise, find the range for which the variable has already been tagged.
706 bool found_tag = false;
707 for (const auto& r : range_lists) {
708 if (r.second.range_tag == existing_tag) {
709 found_tag = true;
710 if (r.first < target_range) {
711 // The variable already has a more restrictive range check, so do nothing.
712 return;
713 }
714 // The range constraint we are trying to impose is more restrictive than the existing range
715 // constraint. It would be difficult to remove an existing range check. Instead, arithmetically copy the
716 // variable and apply a range check to new variable. We do _not_ simply create a
717 // copy-constraint, because that would copy the tag, which exactly corresponds to the old (less
718 // restrictive) range constraint. Instead, we use an arithmetic gate to constrain the value of
719 // the new variable and set the tag (a.k.a. range-constraint) via a new call to
720 // `create_new_range_constraint`.
721 const uint32_t copied_witness = this->add_variable(this->get_variable(variable_index));
722 create_add_gate({ .a = variable_index,
723 .b = copied_witness,
724 .c = this->zero_idx(),
725 .a_scaling = 1,
726 .b_scaling = -1,
727 .c_scaling = 0,
728 .const_scaling = 0 });
729 // Recurse with new witness that has no tag attached.
730 create_small_range_constraint(copied_witness, target_range, msg);
731 return;
732 }
733 }
734 // should never occur
735 BB_ASSERT(found_tag);
736}
737
738template <typename ExecutionTrace> void UltraCircuitBuilder_<ExecutionTrace>::process_range_list(RangeList& list)
739{
740 this->assert_valid_variables(list.variable_indices);
741
742 BB_ASSERT_GT(list.variable_indices.size(), 0U);
743
744 // replace witness-index in variable_indices with the corresponding real-variable-index i.e., if a copy constraint
745 // has been applied on a variable after it was range constrained, this makes sure the indices in list point to the
746 // updated index in the range list so the set equivalence does not fail
747 for (uint32_t& x : list.variable_indices) {
748 x = this->real_variable_index[x];
749 }
750 // Sort `variable_indices` and remove duplicate witness indices to prevent the sorted list set size being wrong!
751 std::sort(list.variable_indices.begin(), list.variable_indices.end());
752 auto back_iterator = std::unique(list.variable_indices.begin(), list.variable_indices.end());
753 list.variable_indices.erase(back_iterator, list.variable_indices.end());
754
755 // Extract the values of each (real) variable into a list to be sorted (in the sense of the range/plookup-style
756 // argument).
757 std::vector<uint32_t> sorted_list;
758 sorted_list.reserve(list.variable_indices.size());
759 for (const auto variable_index : list.variable_indices) {
760 // note that `field_element` is < 32 bits as the corresponding witness has a non-trivial range-constraint.
761 const auto& field_element = this->get_variable(variable_index);
762 const uint32_t shrinked_value = static_cast<uint32_t>(field_element);
763 sorted_list.emplace_back(shrinked_value);
764 }
765
766#ifdef NO_PAR_ALGOS
767 std::sort(sorted_list.begin(), sorted_list.end());
768#else
769 std::sort(std::execution::par_unseq, sorted_list.begin(), sorted_list.end());
770#endif
771 // list must be padded to a multipe of 4 and larger than 4 (gate_width)
772 constexpr size_t gate_width = NUM_WIRES;
773 size_t padding = (gate_width - (list.variable_indices.size() % gate_width)) % gate_width;
774
775 std::vector<uint32_t> indices;
776 indices.reserve(padding + sorted_list.size());
777
778 if (list.variable_indices.size() <= gate_width) {
779 padding += gate_width;
780 }
781 for (size_t i = 0; i < padding; ++i) {
782 indices.emplace_back(this->zero_idx());
783 }
784 // tag the elements in the sorted_list to apply the multiset-equality check implicit in range-constraints.
785 for (const auto sorted_value : sorted_list) {
786 const uint32_t index = this->add_variable(fr(sorted_value));
787 assign_tag(index, list.tau_tag);
788 indices.emplace_back(index);
789 }
790 // constrain the _sorted_ list: starts at 0, ends at `target_range`, consecutive differences in {0, 1, 2, 3}.
791 create_sort_constraint_with_edges(indices, 0, list.target_range);
792}
793
794template <typename ExecutionTrace> void UltraCircuitBuilder_<ExecutionTrace>::process_range_lists()
795{
796 for (auto& i : range_lists) {
797 process_range_list(i.second);
798 }
799}
800
801template <typename ExecutionTrace>
802void UltraCircuitBuilder_<ExecutionTrace>::enforce_small_deltas(const std::vector<uint32_t>& variable_indices)
803{
804 constexpr size_t gate_width = NUM_WIRES;
805 BB_ASSERT_EQ(variable_indices.size() % gate_width, 0U);
806 this->assert_valid_variables(variable_indices);
807
808 for (size_t i = 0; i < variable_indices.size(); i += gate_width) {
809
810 this->increment_num_gates();
811 {
812 auto& block_for_row = blocks.delta_range;
813 GateRowT row{};
814 row.wires = {
815 variable_indices[i], variable_indices[i + 1], variable_indices[i + 2], variable_indices[i + 3]
816 };
817 row.gate_kind = GateKind::DeltaRange;
818 row.gate_value = 1;
819 block_for_row.append_gate(row);
820 }
821 }
822 // dummy gate needed because of widget's check of next row
823 create_unconstrained_gate(blocks.delta_range,
824 variable_indices[variable_indices.size() - 1],
825 this->zero_idx(),
826 this->zero_idx(),
827 this->zero_idx());
828}
829
830// useful to put variables in the witness that aren't already used - e.g. the dummy variables of the range constraint in
831// multiples of four
832template <typename ExecutionTrace>
833void UltraCircuitBuilder_<ExecutionTrace>::create_unconstrained_gates(const std::vector<uint32_t>& variable_index)
834{
835 std::vector<uint32_t> padded_list = variable_index;
836 constexpr size_t gate_width = NUM_WIRES;
837 const uint64_t padding = (gate_width - (padded_list.size() % gate_width)) % gate_width;
838 for (uint64_t i = 0; i < padding; ++i) {
839 padded_list.emplace_back(this->zero_idx());
840 }
841 this->assert_valid_variables(variable_index);
842 this->assert_valid_variables(padded_list);
843
844 for (size_t i = 0; i < padded_list.size(); i += gate_width) {
845 create_unconstrained_gate(
846 blocks.arithmetic, padded_list[i], padded_list[i + 1], padded_list[i + 2], padded_list[i + 3]);
847 }
848}
849
850template <typename ExecutionTrace>
852 const std::vector<uint32_t>& variable_indices, const FF& start, const FF& end)
853{
854 // Convenient to assume size is at least 8 (gate_width = 4) for separate gates for start and end conditions
855 constexpr size_t gate_width = NUM_WIRES;
856 BB_ASSERT_EQ(variable_indices.size() % gate_width, 0U);
857 BB_ASSERT_GT(variable_indices.size(), gate_width);
858 this->assert_valid_variables(variable_indices);
859 // 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`
860 // to be in {0, 1, 2, 3}.
861 auto& block = blocks.delta_range;
862
863 // Add an arithmetic gate to ensure the first input is equal to the start value of the range being checked
864 create_add_gate({ variable_indices[0], this->zero_idx(), this->zero_idx(), 1, 0, 0, -start });
865
866 // enforce delta range relation for all rows (there are `variabe_indices.size() / gate_width`). note that there are
867 // at least two rows.
868 for (size_t i = 0; i < variable_indices.size(); i += gate_width) {
869
870 this->increment_num_gates();
871 {
872 auto& block_for_row = block;
873 GateRowT row{};
874 row.wires = {
875 variable_indices[i], variable_indices[i + 1], variable_indices[i + 2], variable_indices[i + 3]
876 };
877 row.gate_kind = GateKind::DeltaRange;
878 row.gate_value = 1;
879 block_for_row.append_gate(row);
880 }
881 }
882
883 // 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}).
884 // Therefore, we repeat the last element in an unconstrained gate.
885 create_unconstrained_gate(
886 block, variable_indices[variable_indices.size() - 1], this->zero_idx(), this->zero_idx(), this->zero_idx());
887 // arithmetic gate to constrain that `variable_indices[last] == end`, i.e., verify the boundary condition.
888 create_add_gate(
889 { variable_indices[variable_indices.size() - 1], this->zero_idx(), this->zero_idx(), 1, 0, 0, -end });
890}
891
916template <typename ExecutionTrace>
918 const MEMORY_SELECTORS type) const
919{
920 GateRowT row{};
922 row.gate_value = type == MEMORY_SELECTORS::MEM_NONE ? 0 : 1;
923 switch (type) {
924 case MEMORY_SELECTORS::ROM_CONSISTENCY_CHECK: {
925 // Memory read gate used with the sorted list of memory reads.
926 // Apply sorted memory read checks with the following additional check:
927 // 1. Assert that if index field across two gates does not change, the value field does not change.
928 // Used for ROM reads and RAM reads across write/read boundaries
929 row.q_1 = 1;
930 row.q_2 = 1;
931 break;
932 }
933 case MEMORY_SELECTORS::RAM_CONSISTENCY_CHECK: {
934 // Memory read gate used with the sorted list of memory reads.
935 // 1. Validate adjacent index values across 2 gates increases by 0 or 1
936 // 2. Validate record computation (r = read_write_flag + index * \eta + \timestamp * \eta^2 + value * \eta^3)
937 // 3. If adjacent index values across 2 gates does not change, and the next gate's read_write_flag is set to
938 // 'read', validate adjacent values do not change Used for ROM reads and RAM reads across read/write boundaries
939 row.q_3 = 1;
940 break;
941 }
942 case MEMORY_SELECTORS::RAM_TIMESTAMP_CHECK: {
943 // For two adjacent RAM entries that share the same index, validate the timestamp value is monotonically
944 // increasing
945 row.q_1 = 1;
946 row.q_4 = 1;
947 break;
948 }
949 case MEMORY_SELECTORS::ROM_READ: {
950 // Memory read gate for reading memory cells. Also used for the _initialization_ of ROM memory cells.
951 // Validates record witness computation (r = read_write_flag + index * \eta + timestamp * \eta^2 + value *
952 // \eta^3)
953 row.q_1 = 1;
954 row.q_m = 1; // validate record witness is correctly computed
955 // read/write flag stored in q_c
956 break;
957 }
958 case MEMORY_SELECTORS::RAM_READ: {
959 // Memory read gate for reading memory cells.
960 // Validates record witness computation (r = read_write_flag + index * \eta + timestamp * \eta^2 + value *
961 // \eta^3)
962 row.q_1 = 1;
963 row.q_m = 1; // validate record witness is correctly computed
964 // read/write flag stored in q_c
965 break;
966 }
967 case MEMORY_SELECTORS::RAM_WRITE: {
968 // Memory read gate for writing memory cells.
969 // Validates record witness computation (r = read_write_flag + index * \eta + timestamp * \eta^2 + value *
970 // \eta^3)
971 row.q_1 = 1;
972 row.q_m = 1; // validate record witness is correctly computed
973 row.q_c = 1; // read/write flag stored in q_c
974 break;
975 }
976 case MEMORY_SELECTORS::ROM_LOGUP_TABLE: {
977 // Single-value ROM table entry under the LogUp scheme. The row holds
978 // (index, value, multiplicity, inverse) and contributes -m_i * inv to the LogUp sum.
979 // Pattern: q_2 = 1 alone (combined with q_1 = 0) uniquely identifies this gate within
980 // the q_memory-gated context (ROM_CONSISTENCY_CHECK shares q_2 but also sets q_1).
981 row.q_2 = 1;
982 break;
983 }
984 case MEMORY_SELECTORS::ROM_LOGUP_READ: {
985 // Single-value ROM read access under the LogUp scheme. The row holds
986 // (index, value, +1, inverse) and contributes +inv to the LogUp sum.
987 // Pattern: q_4 = 1 alone (combined with q_1 = 0) uniquely identifies this gate within
988 // the q_memory-gated context (RAM_TIMESTAMP_CHECK shares q_4 but also sets q_1).
989 row.q_4 = 1;
990 break;
991 }
992 default: {
993 break;
994 }
995 }
996 return row;
997}
998
1022template <typename ExecutionTrace>
1024 const NNF_SELECTORS type) const
1025{
1026 GateRowT row{};
1028 row.gate_value = type == NNF_SELECTORS::NNF_NONE ? 0 : 1;
1029 switch (type) {
1030 case NNF_SELECTORS::LIMB_ACCUMULATE_1: {
1031 row.q_3 = 1;
1032 row.q_4 = 1;
1033 break;
1034 }
1035 case NNF_SELECTORS::LIMB_ACCUMULATE_2: {
1036 row.q_3 = 1;
1037 row.q_m = 1;
1038 break;
1039 }
1040 case NNF_SELECTORS::NON_NATIVE_FIELD_1: {
1041 row.q_2 = 1;
1042 row.q_3 = 1;
1043 break;
1044 }
1045 case NNF_SELECTORS::NON_NATIVE_FIELD_2: {
1046 row.q_2 = 1;
1047 row.q_4 = 1;
1048 break;
1049 }
1050 case NNF_SELECTORS::NON_NATIVE_FIELD_3: {
1051 row.q_2 = 1;
1052 row.q_m = 1;
1053 break;
1054 }
1055 default: {
1056 break;
1057 }
1058 }
1059 return row;
1060}
1061
1072template <typename ExecutionTrace>
1074 const uint32_t hi_idx,
1075 const size_t lo_limb_bits,
1076 const size_t hi_limb_bits,
1077 std::string_view msg)
1078{
1079 // Validate limbs are <= 70 bits. If limbs are larger we require more witnesses and cannot use our limb accumulation
1080 // custom gate
1081 BB_ASSERT_LTE(lo_limb_bits, 14U * 5U);
1082 BB_ASSERT_LTE(hi_limb_bits, 14U * 5U);
1083
1084 // If the value is larger than the range, we log the error in builder
1085 const bool is_lo_out_of_range = (uint256_t(this->get_variable(lo_idx)) >= (uint256_t(1) << lo_limb_bits));
1086 if (is_lo_out_of_range && !this->failed()) {
1087 this->failure(std::string(msg) + ": lo limb.");
1088 }
1089 const bool is_hi_out_of_range = (uint256_t(this->get_variable(hi_idx)) >= (uint256_t(1) << hi_limb_bits));
1090 if (is_hi_out_of_range && !this->failed()) {
1091 this->failure(std::string(msg) + ": hi limb.");
1092 }
1093
1094 // Sometimes we try to use limbs that are too large. It's easier to catch this issue here
1095 const auto get_sublimbs = [&](const uint32_t& limb_idx, const std::array<uint64_t, 5>& sublimb_masks) {
1096 const uint256_t limb = this->get_variable(limb_idx);
1097 // we can use constant 2^14 - 1 mask here. If the sublimb value exceeds the expected value then witness will
1098 // fail the range check below
1099 // We also use zero_idx to substitute variables that should be zero
1100 constexpr uint256_t MAX_SUBLIMB_MASK = (uint256_t(1) << 14) - 1;
1101 std::array<uint32_t, 5> sublimb_indices;
1102 sublimb_indices[0] = sublimb_masks[0] != 0 ? this->add_variable(fr(limb & MAX_SUBLIMB_MASK)) : this->zero_idx();
1103 sublimb_indices[1] =
1104 sublimb_masks[1] != 0 ? this->add_variable(fr((limb >> 14) & MAX_SUBLIMB_MASK)) : this->zero_idx();
1105 sublimb_indices[2] =
1106 sublimb_masks[2] != 0 ? this->add_variable(fr((limb >> 28) & MAX_SUBLIMB_MASK)) : this->zero_idx();
1107 sublimb_indices[3] =
1108 sublimb_masks[3] != 0 ? this->add_variable(fr((limb >> 42) & MAX_SUBLIMB_MASK)) : this->zero_idx();
1109 sublimb_indices[4] =
1110 sublimb_masks[4] != 0 ? this->add_variable(fr((limb >> 56) & MAX_SUBLIMB_MASK)) : this->zero_idx();
1111 return sublimb_indices;
1112 };
1113
1114 const auto get_limb_masks = [](size_t limb_bits) {
1115 std::array<uint64_t, 5> sublimb_masks;
1116 sublimb_masks[0] = limb_bits >= 14 ? 14 : limb_bits;
1117 sublimb_masks[1] = limb_bits >= 28 ? 14 : (limb_bits > 14 ? limb_bits - 14 : 0);
1118 sublimb_masks[2] = limb_bits >= 42 ? 14 : (limb_bits > 28 ? limb_bits - 28 : 0);
1119 sublimb_masks[3] = limb_bits >= 56 ? 14 : (limb_bits > 42 ? limb_bits - 42 : 0);
1120 sublimb_masks[4] = (limb_bits > 56 ? limb_bits - 56 : 0);
1121
1122 for (auto& mask : sublimb_masks) {
1123 mask = (1ULL << mask) - 1ULL;
1124 }
1125 return sublimb_masks;
1126 };
1127
1128 const auto lo_masks = get_limb_masks(lo_limb_bits);
1129 const auto hi_masks = get_limb_masks(hi_limb_bits);
1130 const std::array<uint32_t, 5> lo_sublimbs = get_sublimbs(lo_idx, lo_masks);
1131 const std::array<uint32_t, 5> hi_sublimbs = get_sublimbs(hi_idx, hi_masks);
1132
1133 {
1134 auto row = nnf_selectors_row(NNF_SELECTORS::LIMB_ACCUMULATE_1);
1135 row.wires = { lo_sublimbs[0], lo_sublimbs[1], lo_sublimbs[2], lo_idx };
1136 blocks.nnf.append_gate(row);
1137 }
1138 {
1139 auto row = nnf_selectors_row(NNF_SELECTORS::LIMB_ACCUMULATE_2);
1140 row.wires = { lo_sublimbs[3], lo_sublimbs[4], hi_sublimbs[0], hi_sublimbs[1] };
1141 blocks.nnf.append_gate(row);
1142 }
1143 {
1144 auto row = nnf_selectors_row(NNF_SELECTORS::NNF_NONE);
1145 row.wires = { hi_sublimbs[2], hi_sublimbs[3], hi_sublimbs[4], hi_idx };
1146 blocks.nnf.append_gate(row);
1147 }
1148 this->increment_num_gates(3);
1149
1150 for (size_t i = 0; i < 5; i++) {
1151 if (lo_masks[i] != 0) {
1152 create_small_range_constraint(
1153 lo_sublimbs[i], lo_masks[i], "ultra_circuit_builder: sublimb of low too large");
1154 }
1155 if (hi_masks[i] != 0) {
1156 create_small_range_constraint(
1157 hi_sublimbs[i], hi_masks[i], "ultra_circuit_builder: sublimb of hi too large");
1158 }
1159 }
1160};
1161
1177template <typename ExecutionTrace>
1180{
1181 const auto [a0, a1, a2, a3] = std::array{ this->get_variable(input.a[0]),
1182 this->get_variable(input.a[1]),
1183 this->get_variable(input.a[2]),
1184 this->get_variable(input.a[3]) };
1185 const auto [b0, b1, b2, b3] = std::array{ this->get_variable(input.b[0]),
1186 this->get_variable(input.b[1]),
1187 this->get_variable(input.b[2]),
1188 this->get_variable(input.b[3]) };
1189 const auto [q0, q1, q2, q3] = std::array{ this->get_variable(input.q[0]),
1190 this->get_variable(input.q[1]),
1191 this->get_variable(input.q[2]),
1192 this->get_variable(input.q[3]) };
1193 const auto [r0, r1, r2, r3] = std::array{ this->get_variable(input.r[0]),
1194 this->get_variable(input.r[1]),
1195 this->get_variable(input.r[2]),
1196 this->get_variable(input.r[3]) };
1197 const auto& p_neg = input.neg_modulus;
1198
1199 constexpr FF LIMB_SHIFT = uint256_t(1) << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS;
1200 constexpr FF LIMB_RSHIFT = FF(1) / FF(uint256_t(1) << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS);
1201 constexpr FF LIMB_RSHIFT_2 = FF(1) / FF(uint256_t(1) << (2 * DEFAULT_NON_NATIVE_FIELD_LIMB_BITS));
1202
1203 // lo_0 = (a0·b0 - r0) + (a1·b0 + a0·b1)·2^L
1204 FF lo_0 = (a0 * b0 - r0) + (a1 * b0 + a0 * b1) * LIMB_SHIFT;
1205 // lo_1 = (lo_0 + q0·p0' + (q1·p0' + q0·p1' - r1)·2^L) / 2^2L
1206 FF lo_1 = (lo_0 + q0 * p_neg[0] + (q1 * p_neg[0] + q0 * p_neg[1] - r1) * LIMB_SHIFT) * LIMB_RSHIFT_2;
1207
1208 // hi_0 = (a2·b0 + a0·b2) + (a0·b3 + a3·b0 - r3)·2^L
1209 FF hi_0 = (a2 * b0 + a0 * b2) + (a0 * b3 + a3 * b0 - r3) * LIMB_SHIFT;
1210 // hi_1 = hi_0 + (a1·b1 - r2) + (a1·b2 + a2·b1)·2^L
1211 FF hi_1 = hi_0 + (a1 * b1 - r2) + (a1 * b2 + a2 * b1) * LIMB_SHIFT;
1212 // hi_2 = hi_1 + lo_1 + q2·p0' + (q3·p0' + q2·p1')·2^L
1213 FF hi_2 = hi_1 + lo_1 + q2 * p_neg[0] + (q3 * p_neg[0] + q2 * p_neg[1]) * LIMB_SHIFT;
1214 // hi_3 = (hi_2 + q0·p2' + q1·p1' + (q0·p3' + q1·p2')·2^L) / 2^2L
1215 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;
1216
1217 const uint32_t lo_0_idx = this->add_variable(lo_0);
1218 const uint32_t lo_1_idx = this->add_variable(lo_1);
1219 const uint32_t hi_0_idx = this->add_variable(hi_0);
1220 const uint32_t hi_1_idx = this->add_variable(hi_1);
1221 const uint32_t hi_2_idx = this->add_variable(hi_2);
1222 const uint32_t hi_3_idx = this->add_variable(hi_3);
1223
1224 // Gate 1: big_add_gate to validate lo_1
1225 // (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
1226 // This constraint requires two rows in the trace: an arithmetic gate plus an unconstrained arithmetic gate
1227 // containing lo_0 in wire 4 so that the previous gate can access it via shifts. (We cannot use the next nnf gate
1228 // for this purpose since our trace is sorted by gate type).
1229 create_big_add_gate({ input.q[0],
1230 input.q[1],
1231 input.r[1],
1232 lo_1_idx,
1233 input.neg_modulus[0] + input.neg_modulus[1] * LIMB_SHIFT,
1234 input.neg_modulus[0] * LIMB_SHIFT,
1235 -LIMB_SHIFT,
1236 -LIMB_SHIFT.sqr(),
1237 0 },
1238 /*include_next_gate_w_4*/ true);
1239 // Gate 2: unconstrained gate to provide lo_0 via w_4_shift for gate 1
1240 create_unconstrained_gate(blocks.arithmetic, this->zero_idx(), this->zero_idx(), this->zero_idx(), lo_0_idx);
1241
1242 //
1243 // a = (a3 || a2 || a1 || a0) = (a3 * 2^b + a2) * 2^b + (a1 * 2^b + a0)
1244 // b = (b3 || b2 || b1 || b0) = (b3 * 2^b + b2) * 2^b + (b1 * 2^b + b0)
1245 //
1246 // Gate 3: NNF gate to check if lo_0 was computed correctly
1247 // The gate structure for the nnf gates is as follows:
1248 //
1249 // | a1 | b1 | r0 | lo_0 | <-- Gate 3: check lo_0
1250 // | a0 | b0 | a3 | b3 |
1251 // | a2 | b2 | r3 | hi_0 |
1252 // | a1 | b1 | r2 | hi_1 |
1253 //
1254 // Constraint: lo_0 = (a1 * b0 + a0 * b1) * 2^b + (a0 * b0) - r0
1255 // w4 = (w1 * w'2 + w'1 * w2) * 2^b + (w'1 * w'2) - w3
1256 //
1257 {
1258 auto row = nnf_selectors_row(NNF_SELECTORS::NON_NATIVE_FIELD_1);
1259 row.wires = { input.a[1], input.b[1], input.r[0], lo_0_idx };
1260 blocks.nnf.append_gate(row);
1261 }
1262 this->increment_num_gates();
1263
1264 //
1265 // Gate 4: NNF gate to check if hi_0 was computed correctly
1266 //
1267 // | a1 | b1 | r0 | lo_0 |
1268 // | a0 | b0 | a3 | b3 | <-- Gate 4: check hi_0
1269 // | a2 | b2 | r3 | hi_0 |
1270 // | a1 | b1 | r2 | hi_1 |
1271 //
1272 // Constraint: hi_0 = (a0 * b3 + a3 * b0 - r3) * 2^b + (a0 * b2 + a2 * b0)
1273 // w'4 = (w1 * w4 + w2 * w3 - w'3) * 2^b + (w1 * w'2 + w'1 * w2)
1274 //
1275 {
1276 auto row = nnf_selectors_row(NNF_SELECTORS::NON_NATIVE_FIELD_2);
1277 row.wires = { input.a[0], input.b[0], input.a[3], input.b[3] };
1278 blocks.nnf.append_gate(row);
1279 }
1280 this->increment_num_gates();
1281
1282 //
1283 // Gate 5: NNF gate to check if hi_1 was computed correctly
1284 //
1285 // | a1 | b1 | r0 | lo_0 |
1286 // | a0 | b0 | a3 | b3 |
1287 // | a2 | b2 | r3 | hi_0 | <-- Gate 5: check hi_1
1288 // | a1 | b1 | r2 | hi_1 |
1289 //
1290 // Constraint: hi_1 = hi_0 + (a2 * b1 + a1 * b2) * 2^b + (a1 * b1) - r2
1291 // w'4 = w4 + (w1 * w'2 + w'1 * w2) * 2^b + (w'1 * w'2) - w'3
1292 //
1293 {
1294 auto row = nnf_selectors_row(NNF_SELECTORS::NON_NATIVE_FIELD_3);
1295 row.wires = { input.a[2], input.b[2], input.r[3], hi_0_idx };
1296 blocks.nnf.append_gate(row);
1297 }
1298 this->increment_num_gates();
1299
1300 //
1301 // Gate 6: NNF gate with no constraints (q_nnf=0, truly unconstrained)
1302 // Provides values a[1], b[1], r[2], hi_1 to Gate 5 via shifts (w'1, w'2, w'3, w'4)
1303 //
1304 {
1305 auto row = nnf_selectors_row(NNF_SELECTORS::NNF_NONE);
1306 row.wires = { input.a[1], input.b[1], input.r[2], hi_1_idx };
1307 blocks.nnf.append_gate(row);
1308 }
1309 this->increment_num_gates();
1310
1311 //
1312 // Gate 7: big_add_gate to validate hi_2
1313 //
1314 // hi_2 - hi_1 - lo_1 - q[2](p[1].2^b + p[0]) - q[3](p[0].2^b) = 0
1315 //
1316 create_big_add_gate(
1317 {
1318 input.q[2],
1319 input.q[3],
1320 lo_1_idx,
1321 hi_1_idx,
1322 -input.neg_modulus[1] * LIMB_SHIFT - input.neg_modulus[0],
1323 -input.neg_modulus[0] * LIMB_SHIFT,
1324 -1,
1325 -1,
1326 0,
1327 },
1328 /*include_next_gate_w_4*/ true);
1329
1330 //
1331 // Gate 8: big_add_gate to validate hi_3 (provides hi_2 in w_4 for gate 7)
1332 //
1333 // hi_3 - (hi_2 - q[0](p[3].2^b + p[2]) - q[1](p[2].2^b + p[1])).2^-2b = 0
1334 //
1335 create_big_add_gate({
1336 hi_3_idx,
1337 input.q[0],
1338 input.q[1],
1339 hi_2_idx,
1340 -1,
1341 input.neg_modulus[3] * LIMB_RSHIFT + input.neg_modulus[2] * LIMB_RSHIFT_2,
1342 input.neg_modulus[2] * LIMB_RSHIFT + input.neg_modulus[1] * LIMB_RSHIFT_2,
1343 LIMB_RSHIFT_2,
1344 0,
1345 });
1346
1347 return std::array<uint32_t, 2>{ lo_1_idx, hi_3_idx };
1348}
1349
1357{
1358 for (size_t i = 0; i < cached_partial_non_native_field_multiplications.size(); ++i) {
1359 auto& c = cached_partial_non_native_field_multiplications[i];
1360 for (size_t j = 0; j < c.a.size(); ++j) {
1361 c.a[j] = this->real_variable_index[c.a[j]];
1362 c.b[j] = this->real_variable_index[c.b[j]];
1363 }
1364 }
1365 cached_partial_non_native_field_multiplication::deduplicate(cached_partial_non_native_field_multiplications, this);
1366
1367 // iterate over the cached items and create constraints
1368 for (const auto& input : cached_partial_non_native_field_multiplications) {
1369
1370 {
1371 auto row = nnf_selectors_row(NNF_SELECTORS::NON_NATIVE_FIELD_1);
1372 row.wires = { input.a[1], input.b[1], this->zero_idx(), input.lo_0 };
1373 blocks.nnf.append_gate(row);
1374 }
1375 this->increment_num_gates();
1376
1377 {
1378 auto row = nnf_selectors_row(NNF_SELECTORS::NON_NATIVE_FIELD_2);
1379 row.wires = { input.a[0], input.b[0], input.a[3], input.b[3] };
1380 blocks.nnf.append_gate(row);
1381 }
1382 this->increment_num_gates();
1383
1384 {
1385 auto row = nnf_selectors_row(NNF_SELECTORS::NON_NATIVE_FIELD_3);
1386 row.wires = { input.a[2], input.b[2], this->zero_idx(), input.hi_0 };
1387 blocks.nnf.append_gate(row);
1388 }
1389 this->increment_num_gates();
1390
1391 {
1392 auto row = nnf_selectors_row(NNF_SELECTORS::NNF_NONE);
1393 row.wires = { input.a[1], input.b[1], this->zero_idx(), input.hi_1 };
1394 blocks.nnf.append_gate(row);
1395 }
1396 this->increment_num_gates();
1397 }
1398}
1399
1406template <typename ExecutionTrace>
1409{
1411 this->get_variable(input.a[0]),
1412 this->get_variable(input.a[1]),
1413 this->get_variable(input.a[2]),
1414 this->get_variable(input.a[3]),
1415 };
1417 this->get_variable(input.b[0]),
1418 this->get_variable(input.b[1]),
1419 this->get_variable(input.b[2]),
1420 this->get_variable(input.b[3]),
1421 };
1422
1423 constexpr FF LIMB_SHIFT = uint256_t(1) << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS;
1424
1425 FF lo_0 = a[0] * b[0] + ((a[1] * b[0] + a[0] * b[1]) * LIMB_SHIFT);
1426 FF hi_0 = a[2] * b[0] + a[0] * b[2] + ((a[0] * b[3] + a[3] * b[0]) * LIMB_SHIFT);
1427 FF hi_1 = hi_0 + a[1] * b[1] + ((a[1] * b[2] + a[2] * b[1]) * LIMB_SHIFT);
1428
1429 const uint32_t lo_0_idx = this->add_variable(lo_0);
1430 const uint32_t hi_0_idx = this->add_variable(hi_0);
1431 const uint32_t hi_1_idx = this->add_variable(hi_1);
1432
1433 // Add witnesses into the multiplication cache (duplicates removed during circuit finalization)
1435 .a = input.a,
1436 .b = input.b,
1437 .lo_0 = lo_0_idx,
1438 .hi_0 = hi_0_idx,
1439 .hi_1 = hi_1_idx,
1440 };
1441 cached_partial_non_native_field_multiplications.emplace_back(cache_entry);
1442 return std::array<uint32_t, 2>{ lo_0_idx, hi_1_idx };
1443}
1444
1450template <typename ExecutionTrace>
1453{
1454 const uint32_t& x_0 = std::get<0>(limb0).first;
1455 const uint32_t& x_1 = std::get<0>(limb1).first;
1456 const uint32_t& x_2 = std::get<0>(limb2).first;
1457 const uint32_t& x_3 = std::get<0>(limb3).first;
1458 const uint32_t& x_p = std::get<0>(limbp);
1459
1460 const FF& x_mulconst0 = std::get<0>(limb0).second;
1461 const FF& x_mulconst1 = std::get<0>(limb1).second;
1462 const FF& x_mulconst2 = std::get<0>(limb2).second;
1463 const FF& x_mulconst3 = std::get<0>(limb3).second;
1464
1465 const uint32_t& y_0 = std::get<1>(limb0).first;
1466 const uint32_t& y_1 = std::get<1>(limb1).first;
1467 const uint32_t& y_2 = std::get<1>(limb2).first;
1468 const uint32_t& y_3 = std::get<1>(limb3).first;
1469 const uint32_t& y_p = std::get<1>(limbp);
1470
1471 const FF& y_mulconst0 = std::get<1>(limb0).second;
1472 const FF& y_mulconst1 = std::get<1>(limb1).second;
1473 const FF& y_mulconst2 = std::get<1>(limb2).second;
1474 const FF& y_mulconst3 = std::get<1>(limb3).second;
1475
1476 // constant additive terms
1477 const FF& addconst0 = std::get<2>(limb0);
1478 const FF& addconst1 = std::get<2>(limb1);
1479 const FF& addconst2 = std::get<2>(limb2);
1480 const FF& addconst3 = std::get<2>(limb3);
1481 const FF& addconstp = std::get<2>(limbp);
1482
1483 // get value of result limbs
1484 const FF z_0value = (this->get_variable(x_0) * x_mulconst0) + (this->get_variable(y_0) * y_mulconst0) + addconst0;
1485 const FF z_1value = (this->get_variable(x_1) * x_mulconst1) + (this->get_variable(y_1) * y_mulconst1) + addconst1;
1486 const FF z_2value = (this->get_variable(x_2) * x_mulconst2) + (this->get_variable(y_2) * y_mulconst2) + addconst2;
1487 const FF z_3value = (this->get_variable(x_3) * x_mulconst3) + (this->get_variable(y_3) * y_mulconst3) + addconst3;
1488 const FF z_pvalue = this->get_variable(x_p) + this->get_variable(y_p) + addconstp;
1489
1490 const uint32_t z_0 = this->add_variable(z_0value);
1491 const uint32_t z_1 = this->add_variable(z_1value);
1492 const uint32_t z_2 = this->add_variable(z_2value);
1493 const uint32_t z_3 = this->add_variable(z_3value);
1494 const uint32_t z_p = this->add_variable(z_pvalue);
1495
1517 auto& block = blocks.arithmetic;
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 {
1524 auto& block_for_row = block;
1525 GateRowT row{};
1526 row.wires = { y_p, x_0, y_0, x_p };
1527 row.q_m = addconstp;
1528 row.q_c = -addconst0 * linear_term_scale_factor;
1529 row.q_2 = -x_mulconst0 * linear_term_scale_factor;
1530 row.q_3 = -y_mulconst0 * linear_term_scale_factor;
1531 row.gate_kind = GateKind::Arith;
1532 row.gate_value = 3;
1533 block_for_row.append_gate(row);
1534 }
1535
1536 {
1537 auto& block_for_row = block;
1538 GateRowT row{};
1539 row.wires = { z_p, x_1, y_1, z_0 };
1540 row.q_c = -addconst1;
1541 row.q_2 = -x_mulconst1;
1542 row.q_3 = -y_mulconst1;
1543 row.gate_kind = GateKind::Arith;
1544 row.gate_value = 2;
1545 block_for_row.append_gate(row);
1546 }
1547
1548 {
1549 auto& block_for_row = block;
1550 GateRowT row{};
1551 row.wires = { x_2, y_2, z_2, z_1 };
1552 row.q_c = -addconst2;
1553 row.q_1 = -x_mulconst2;
1554 row.q_2 = -y_mulconst2;
1555 row.q_3 = 1;
1556 row.gate_kind = GateKind::Arith;
1557 row.gate_value = 1;
1558 block_for_row.append_gate(row);
1559 }
1560
1561 {
1562 auto& block_for_row = block;
1563 GateRowT row{};
1564 row.wires = { x_3, y_3, z_3, this->zero_idx() };
1565 row.q_c = -addconst3;
1566 row.q_1 = -x_mulconst3;
1567 row.q_2 = -y_mulconst3;
1568 row.q_3 = 1;
1569 row.gate_kind = GateKind::Arith;
1570 row.gate_value = 1;
1571 block_for_row.append_gate(row);
1572 }
1573
1574 this->increment_num_gates(4);
1576 z_0, z_1, z_2, z_3, z_p,
1577 };
1578}
1579
1585template <typename ExecutionTrace>
1588{
1589 const uint32_t& x_0 = std::get<0>(limb0).first;
1590 const uint32_t& x_1 = std::get<0>(limb1).first;
1591 const uint32_t& x_2 = std::get<0>(limb2).first;
1592 const uint32_t& x_3 = std::get<0>(limb3).first;
1593 const uint32_t& x_p = std::get<0>(limbp);
1594
1595 const FF& x_mulconst0 = std::get<0>(limb0).second;
1596 const FF& x_mulconst1 = std::get<0>(limb1).second;
1597 const FF& x_mulconst2 = std::get<0>(limb2).second;
1598 const FF& x_mulconst3 = std::get<0>(limb3).second;
1599
1600 const uint32_t& y_0 = std::get<1>(limb0).first;
1601 const uint32_t& y_1 = std::get<1>(limb1).first;
1602 const uint32_t& y_2 = std::get<1>(limb2).first;
1603 const uint32_t& y_3 = std::get<1>(limb3).first;
1604 const uint32_t& y_p = std::get<1>(limbp);
1605
1606 const FF& y_mulconst0 = std::get<1>(limb0).second;
1607 const FF& y_mulconst1 = std::get<1>(limb1).second;
1608 const FF& y_mulconst2 = std::get<1>(limb2).second;
1609 const FF& y_mulconst3 = std::get<1>(limb3).second;
1610
1611 // constant additive terms
1612 const FF& addconst0 = std::get<2>(limb0);
1613 const FF& addconst1 = std::get<2>(limb1);
1614 const FF& addconst2 = std::get<2>(limb2);
1615 const FF& addconst3 = std::get<2>(limb3);
1616 const FF& addconstp = std::get<2>(limbp);
1617
1618 // get value of result limbs
1619 const FF z_0value = (this->get_variable(x_0) * x_mulconst0) - (this->get_variable(y_0) * y_mulconst0) + addconst0;
1620 const FF z_1value = (this->get_variable(x_1) * x_mulconst1) - (this->get_variable(y_1) * y_mulconst1) + addconst1;
1621 const FF z_2value = (this->get_variable(x_2) * x_mulconst2) - (this->get_variable(y_2) * y_mulconst2) + addconst2;
1622 const FF z_3value = (this->get_variable(x_3) * x_mulconst3) - (this->get_variable(y_3) * y_mulconst3) + addconst3;
1623 const FF z_pvalue = this->get_variable(x_p) - this->get_variable(y_p) + addconstp;
1624
1625 const uint32_t z_0 = this->add_variable(z_0value);
1626 const uint32_t z_1 = this->add_variable(z_1value);
1627 const uint32_t z_2 = this->add_variable(z_2value);
1628 const uint32_t z_3 = this->add_variable(z_3value);
1629 const uint32_t z_p = this->add_variable(z_pvalue);
1630
1655 auto& block = blocks.arithmetic;
1656
1657 // When q_arith == 3, w_4_shift is scaled by 2 (see ArithmeticRelation for details). Therefore, for consistency we
1658 // also scale each linear term by this factor of 2 so that the constraint is effectively:
1659 // (q_l * w_1) + (q_r * w_2) + (q_o * w_3) + (q_4 * w_4) + q_c + w_4_shift = 0
1660 const FF linear_term_scale_factor = 2;
1661 {
1662 auto& block_for_row = block;
1663 GateRowT row{};
1664 row.wires = { y_p, x_0, y_0, z_p };
1665 row.q_m = -addconstp;
1666 row.q_c = -addconst0 * linear_term_scale_factor;
1667 row.q_2 = -x_mulconst0 * linear_term_scale_factor;
1668 row.q_3 = y_mulconst0 * linear_term_scale_factor;
1669 row.gate_kind = GateKind::Arith;
1670 row.gate_value = 3;
1671 block_for_row.append_gate(row);
1672 }
1673
1674 {
1675 auto& block_for_row = block;
1676 GateRowT row{};
1677 row.wires = { x_p, x_1, y_1, z_0 };
1678 row.q_c = -addconst1;
1679 row.q_2 = -x_mulconst1;
1680 row.q_3 = y_mulconst1;
1681 row.gate_kind = GateKind::Arith;
1682 row.gate_value = 2;
1683 block_for_row.append_gate(row);
1684 }
1685
1686 {
1687 auto& block_for_row = block;
1688 GateRowT row{};
1689 row.wires = { x_2, y_2, z_2, z_1 };
1690 row.q_c = -addconst2;
1691 row.q_1 = -x_mulconst2;
1692 row.q_2 = y_mulconst2;
1693 row.q_3 = 1;
1694 row.gate_kind = GateKind::Arith;
1695 row.gate_value = 1;
1696 block_for_row.append_gate(row);
1697 }
1698
1699 {
1700 auto& block_for_row = block;
1701 GateRowT row{};
1702 row.wires = { x_3, y_3, z_3, this->zero_idx() };
1703 row.q_c = -addconst3;
1704 row.q_1 = -x_mulconst3;
1705 row.q_2 = y_mulconst3;
1706 row.q_3 = 1;
1707 row.gate_kind = GateKind::Arith;
1708 row.gate_value = 1;
1709 block_for_row.append_gate(row);
1710 }
1711
1712 this->increment_num_gates(4);
1714 z_0, z_1, z_2, z_3, z_p,
1715 };
1716}
1717
1727template <typename ExecutionTrace>
1729{
1730 return this->rom_ram_logic.create_ROM_array(array_size);
1731}
1732
1742template <typename ExecutionTrace>
1744{
1745 return this->rom_ram_logic.create_RAM_array(array_size);
1746}
1747
1755template <typename ExecutionTrace>
1757 const size_t index_value,
1758 const uint32_t value_witness)
1759{
1760 this->rom_ram_logic.init_RAM_element(this, ram_id, index_value, value_witness);
1761}
1762
1763template <typename ExecutionTrace>
1764uint32_t UltraCircuitBuilder_<ExecutionTrace>::read_RAM_array(const size_t ram_id, const uint32_t index_witness)
1765{
1766 return this->rom_ram_logic.read_RAM_array(this, ram_id, index_witness);
1767}
1768
1769template <typename ExecutionTrace>
1771 const uint32_t index_witness,
1772 const uint32_t value_witness)
1773{
1774 this->rom_ram_logic.write_RAM_array(this, ram_id, index_witness, value_witness);
1775}
1776
1792template <typename ExecutionTrace>
1794 const size_t index_value,
1795 const uint32_t value_witness)
1796{
1797 this->rom_ram_logic.set_ROM_element(this, rom_id, index_value, value_witness);
1798}
1799
1807template <typename ExecutionTrace>
1809 const size_t index_value,
1810 const std::array<uint32_t, 2>& value_witnesses)
1811{
1812 this->rom_ram_logic.set_ROM_element_pair(this, rom_id, index_value, value_witnesses);
1813}
1814
1822template <typename ExecutionTrace>
1823uint32_t UltraCircuitBuilder_<ExecutionTrace>::read_ROM_array(const size_t rom_id, const uint32_t index_witness)
1824{
1825 return this->rom_ram_logic.read_ROM_array(this, rom_id, index_witness);
1826}
1827
1835template <typename ExecutionTrace>
1836std::array<uint32_t, 2> UltraCircuitBuilder_<ExecutionTrace>::read_ROM_array_pair(const size_t rom_id,
1837 const uint32_t index_witness)
1838{
1839 return this->rom_ram_logic.read_ROM_array_pair(this, rom_id, index_witness);
1840}
1841
1847template <typename FF>
1849{
1850 if constexpr (requires { this->blocks.poseidon2_external; }) {
1851 auto& block = this->blocks.poseidon2_external;
1852 block.append_gate({ .wires = { in.a, in.b, in.c, in.d },
1857 .gate_kind = GateKind::Poseidon2Ext,
1858 .gate_value = 1 });
1859 this->increment_num_gates();
1860 } else {
1861 throw_or_abort("create_poseidon2_external_gate base is Ultra-only (Mega overrides into its poseidon2 block)");
1862 }
1863}
1864
1869template <typename FF>
1871{
1872 if constexpr (requires { this->blocks.poseidon2_internal; }) {
1873 auto& block = this->blocks.poseidon2_internal;
1874 {
1875 auto& block_for_row = block;
1876 GateRowT row{};
1877 row.wires = { in.a, in.b, in.c, in.d };
1879 row.gate_kind = GateKind::Poseidon2Int;
1880 row.gate_value = 1;
1881 block_for_row.append_gate(row);
1882 }
1883 this->increment_num_gates();
1884 } else {
1885 throw_or_abort("create_poseidon2_internal_gate is Ultra-only (Mega uses the compressed block)");
1886 }
1887}
1888
1895template <typename ExecutionTrace> msgpack::sbuffer UltraCircuitBuilder_<ExecutionTrace>::export_circuit()
1896{
1897 // You should not name `zero` by yourself
1898 // but it will be rewritten anyway
1899 auto first_zero_idx = this->get_first_variable_in_class(this->zero_idx());
1900 if (!this->variable_names.contains(first_zero_idx)) {
1901 this->set_variable_name(this->zero_idx(), "zero");
1902 } else {
1903 this->variable_names[first_zero_idx] = "zero";
1904 }
1905 using base = CircuitBuilderBase<FF>;
1907
1908 std::array<uint64_t, 4> modulus = {
1909 FF::Params::modulus_0, FF::Params::modulus_1, FF::Params::modulus_2, FF::Params::modulus_3
1910 };
1911 std::stringstream buf;
1912 buf << std::hex << std::setfill('0') << std::setw(16) << modulus[3] << std::setw(16) << modulus[2] << std::setw(16)
1913 << modulus[1] << std::setw(16) << modulus[0];
1914
1915 cir.modulus = buf.str();
1916
1917 for (uint32_t i = 0; i < this->num_public_inputs(); i++) {
1918 cir.public_inps.push_back(this->real_variable_index[this->public_inputs()[i]]);
1919 }
1920
1921 for (auto& tup : base::variable_names) {
1922 cir.vars_of_interest.insert({ this->real_variable_index[tup.first], tup.second });
1923 }
1924
1925 for (const auto& var : this->get_variables()) {
1926 cir.variables.push_back(var);
1927 }
1928
1929 FF curve_b;
1930 if constexpr (FF::modulus == bb::fq::modulus) {
1931 curve_b = bb::g1::curve_b;
1932 } else if constexpr (FF::modulus == grumpkin::fq::modulus) {
1933 curve_b = grumpkin::g1::curve_b;
1934 } else {
1935 curve_b = 0;
1936 }
1937
1938 for (auto& block : blocks.get()) {
1939 std::vector<std::vector<FF>> block_selectors;
1941 for (size_t idx = 0; idx < block.size(); ++idx) {
1942 std::vector<FF> tmp_sel = { block.q_m()[idx],
1943 block.q_1()[idx],
1944 block.q_2()[idx],
1945 block.q_3()[idx],
1946 block.q_4()[idx],
1947 block.q_c()[idx],
1952 read_gate_selector(block, GateKind::Nnf, idx),
1954 curve_b };
1955
1956 std::vector<uint32_t> tmp_w = {
1957 this->real_variable_index[block.w_l()[idx]],
1958 this->real_variable_index[block.w_r()[idx]],
1959 this->real_variable_index[block.w_o()[idx]],
1960 this->real_variable_index[block.w_4()[idx]],
1961 };
1962
1963 if (idx < block.size() - 1) {
1964 tmp_w.push_back(this->real_variable_index[block.w_l()[idx + 1]]);
1965 tmp_w.push_back(this->real_variable_index[block.w_r()[idx + 1]]);
1966 tmp_w.push_back(this->real_variable_index[block.w_o()[idx + 1]]);
1967 tmp_w.push_back(this->real_variable_index[block.w_4()[idx + 1]]);
1968 } else {
1969 tmp_w.push_back(0);
1970 tmp_w.push_back(0);
1971 tmp_w.push_back(0);
1972 tmp_w.push_back(0);
1973 }
1974
1975 block_selectors.push_back(tmp_sel);
1976 block_wires.push_back(tmp_w);
1977 }
1978 cir.selectors.push_back(block_selectors);
1979 cir.wires.push_back(block_wires);
1980 }
1981
1982 cir.real_variable_index = this->real_variable_index;
1983
1984 for (const auto& table : this->lookup_tables) {
1985 info("Table no: ", table.table_index);
1986 std::vector<std::vector<FF>> tmp_table;
1987 for (size_t i = 0; i < table.size(); ++i) {
1988 tmp_table.push_back({ table.column_1[i], table.column_2[i], table.column_3[i] });
1989 }
1990 cir.lookup_tables.push_back(tmp_table);
1991 }
1992
1993 cir.real_variable_tags = this->real_variable_tags;
1994
1995 for (const auto& list : range_lists) {
1996 cir.range_tags[list.second.range_tag] = list.first;
1997 }
1998
1999 for (auto& rom_table : this->rom_ram_logic.rom_arrays) {
2000 std::sort(rom_table.records.begin(), rom_table.records.end());
2001
2003 table.reserve(rom_table.records.size());
2004 for (const auto& rom_entry : rom_table.records) {
2005 table.push_back({
2006 this->real_variable_index[rom_entry.index_witness],
2007 this->real_variable_index[rom_entry.value_column1_witness],
2008 this->real_variable_index[rom_entry.value_column2_witness],
2009 });
2010 }
2011 cir.rom_records.push_back(table);
2012 cir.rom_states.push_back(rom_table.state);
2013 }
2014
2015 for (auto& ram_table : this->rom_ram_logic.ram_arrays) {
2016 std::sort(ram_table.records.begin(), ram_table.records.end());
2017
2019 table.reserve(ram_table.records.size());
2020 for (const auto& ram_entry : ram_table.records) {
2021 table.push_back({ this->real_variable_index[ram_entry.index_witness],
2022 this->real_variable_index[ram_entry.value_witness],
2023 this->real_variable_index[ram_entry.timestamp_witness],
2024 ram_entry.access_type });
2025 }
2026 cir.ram_records.push_back(table);
2027 cir.ram_states.push_back(ram_table.state);
2028 }
2029
2030 cir.circuit_finalized = this->circuit_finalized;
2031
2032 msgpack::sbuffer buffer;
2033 msgpack::pack(buffer, cir);
2034 return buffer;
2035}
2036
2039
2040} // 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....
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_view msg="create_limbed_range_constraint")
Range-constrain a variable to [0, 2^num_bits - 1] by decomposing into smaller limbs.
size_t create_RAM_array(const size_t array_size)
Create a new updatable memory region.
void create_small_range_constraint(const uint32_t variable_index, const uint64_t target_range, std::string_view msg="create_small_range_constraint")
Range-constraints for small ranges, where the upper bound (target_range) need not be dyadic....
void create_big_mul_add_gate(const mul_quad_< FF > &in, const bool use_next_gate_w_4=false)
Create a big multiplication-addition gate, where in.a * in.b * in.mul_scaling + in....
std::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.
GateRowT memory_selectors_row(const MEMORY_SELECTORS type) const
Enable the memory gate of particular type.
plookup::BasicTable * register_basic_lookup_table(plookup::BasicTable &&table)
Register a BasicTable with the builder, assigning it a unique table_index.
GateRowT nnf_selectors_row(const NNF_SELECTORS type) const
Enable the nnf gate of particular type.
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.
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 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 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_view msg="range_constrain_two_limbs")
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.
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 process_non_native_field_multiplications()
Iterates over the cached_non_native_field_multiplication objects, removes duplicates,...
void create_bilinear_batched_eq_gate(const bilinear_batched_eq_gate_< FF > &in)
Create a bilinear / batched-eq gate.
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:377
std::vector< BasicTable::LookupEntry > lookup_entries
Definition types.hpp:383
#define info(...)
Definition log.hpp:93
FF a
FF b
std::unique_ptr< uint8_t[]> buffer
Definition engine.cpp:60
AffineElement * accumulator
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 the block does not own this kind or t...
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
One gate: its wire indices, the non-gate selectors present on every block (see NON_GATE_SELECTORS),...
std::array< uint32_t, NUM_WIRES > wires
Used to store instructions to create partial_non_native_field_multiplication gates.
BilinearBatchedEqMode mode
Definition gate_data.hpp:61
static constexpr std::array< std::array< FF, t >, rounds_f+rounds_p > round_constants
static constexpr uint256_t modulus
Definition types.hpp:306
A basic table from which we can perform lookups (for example, an xor table)
Definition types.hpp:305
std::vector< LookupEntry > lookup_gates
Definition types.hpp:341
size_t size() const
Definition types.hpp:352
std::vector< bb::fr > column_3
Definition types.hpp:340
std::vector< bb::fr > column_2
Definition types.hpp:339
std::vector< bb::fr > column_1
Definition types.hpp:338
void throw_or_abort(std::string const &err)
BB_VF_LOAD_LIMBS * this
VectorField result