Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
ultra_circuit_checker.cpp
Go to the documentation of this file.
5#include <unordered_set>
6
7namespace bb {
8
9template <> auto UltraCircuitChecker::init_empty_values<UltraCircuitBuilder_<UltraExecutionTraceBlocks>>()
10{
12}
13
14template <> auto UltraCircuitChecker::init_empty_values<MegaCircuitBuilder_<bb::fr>>()
15{
16 return MegaFlavor::AllValues{};
17}
18
19template <>
22{
23 // Create a copy of the input circuit
25 if (!builder.circuit_finalized) { // avoid warnings about finalizing an already finalized circuit
26 builder.finalize_circuit(/*ensure_nonzero=*/true); // Test the ensure_nonzero gates as well
27 }
28
29 return builder;
30}
31
32template <>
33MegaCircuitBuilder_<bb::fr> UltraCircuitChecker::prepare_circuit<MegaCircuitBuilder_<bb::fr>>(
34 const MegaCircuitBuilder_<bb::fr>& builder_in)
35{
36 // Create a copy of the input circuit
37 MegaCircuitBuilder_<bb::fr> builder{ builder_in };
38
39 // Deepcopy the opqueue to avoid modifying the original one
40 builder.op_queue = std::make_shared<ECCOpQueue>(*builder.op_queue);
41
42 if (!builder.circuit_finalized) { // avoid warnings about finalizing an already finalized circuit
43 builder.finalize_circuit(/*ensure_nonzero=*/true); // Test the ensure_nonzero gates as well
44 }
45
46 return builder;
47}
48
49template <typename Builder> bool UltraCircuitChecker::check(const Builder& builder_in)
50{
52
53 // Construct a hash table for lookup table entries to efficiently determine if a lookup gate is valid
54 LookupHashTable lookup_hash_table;
55 for (const auto& table : builder.get_lookup_tables()) {
56 const FF table_index(table.table_index);
57 for (size_t i = 0; i < table.size(); ++i) {
58 lookup_hash_table.insert({ table.column_1[i], table.column_2[i], table.column_3[i], table_index });
59 }
60 }
61
62 // Instantiate structs used for checking tag and memory record correctness
63 TagCheckData tag_data;
64 MemoryCheckData memory_data{ builder };
65
66 bool result = true;
67 size_t block_idx = 0;
68 for (auto& block : builder.blocks.get()) {
69 result = result && check_block(builder, block, tag_data, memory_data, lookup_hash_table);
70 if (!result) {
71#ifndef FUZZING_DISABLE_WARNINGS
72 info("Failed at block idx = ", block_idx);
73#else
74 (void)block_idx;
75#endif
76 return false;
77 }
78 block_idx++;
79 }
80
81#ifdef ULTRA_FUZZ
82 result = result & relaxed_check_delta_range_relation(builder);
83 if (!result) {
84 return false;
85 }
86 result = result & relaxed_check_memory_relation(builder);
87 if (!result) {
88 return false;
89 }
90#endif
91#ifndef ULTRA_FUZZ
92 // Tag check is only expected to pass after entire execution trace (all blocks) have been processed
93 result = result && check_tag_data(tag_data);
94 if (!result) {
95 info("Failed tag check.");
96 return false;
97 }
98#endif
99
100 return result;
101};
102
103template <typename Builder>
105 auto& block,
106 TagCheckData& tag_data,
107 MemoryCheckData& memory_data,
108 LookupHashTable& lookup_hash_table)
109{
110 // Initialize empty AllValues of the correct Flavor based on Builder type; for input to Relation::accumulate
111 auto values = init_empty_values<Builder>();
112 Params params;
113 params.eta = memory_data.eta; // used in Memory relation for RAM/ROM consistency
114 params.eta_two = memory_data.eta_two;
115 params.eta_three = memory_data.eta_three;
116
117 auto report_fail = [&](const char* message, size_t row_idx) {
118#ifndef FUZZING_DISABLE_WARNINGS
119 info(message, row_idx);
120#else
121 (void)message;
122 (void)row_idx;
123#endif
124#ifdef CHECK_CIRCUIT_STACKTRACES
125 block.stack_traces.print(row_idx);
126#endif
127 return false;
128 };
129
130 // Perform checks on each gate defined in the builder
131 bool result = true;
132 for (size_t idx = 0; idx < block.size(); ++idx) {
133
134 populate_values(builder, block, values, tag_data, memory_data, idx);
135
136 result = result && check_relation<Arithmetic>(values, params);
137 if (!result) {
138 return report_fail("Failed Arithmetic relation at row idx = ", idx);
139 }
140 result = result && check_relation<Elliptic>(values, params);
141 if (!result) {
142 return report_fail("Failed Elliptic relation at row idx = ", idx);
143 }
144#ifndef ULTRA_FUZZ
145 result = result && check_relation<Memory>(values, params);
146 if (!result) {
147 return report_fail("Failed Memory relation at row idx = ", idx);
148 }
149 result = result && check_relation<NonNativeField>(values, params);
150 if (!result) {
151 return report_fail("Failed NonNativeField relation at row idx = ", idx);
152 }
153 result = result && check_relation<DeltaRangeConstraint>(values, params);
154 if (!result) {
155 return report_fail("Failed DeltaRangeConstraint relation at row idx = ", idx);
156 }
157#else
158 // Bigfield related nnf gates
159 if (values.q_nnf == 1) {
160 bool f0 = values.q_o == 1 && (values.q_4 == 1 || values.q_m == 1);
161 bool f1 = values.q_r == 1 && (values.q_o == 1 || values.q_4 == 1 || values.q_m == 1);
162 if (f0 && f1) {
163 result = result && check_relation<NonNativeField>(values, params);
164 if (!result) {
165 return report_fail("Failed NonNativeField relation at row idx = ", idx);
166 }
167 }
168 }
169#endif
170 result = result && check_lookup(values, lookup_hash_table);
171 if (!result) {
172 return report_fail("Failed Lookup check relation at row idx = ", idx);
173 }
174 result = result && check_relation<PoseidonInternal>(values, params);
175 if (!result) {
176 return report_fail("Failed PoseidonInternal relation at row idx = ", idx);
177 }
178 result = result && check_relation<PoseidonExternal>(values, params);
179 if (!result) {
180 return report_fail("Failed PoseidonExternal relation at row idx = ", idx);
181 }
182
183 if constexpr (IsMegaBuilder<Builder>) {
184 result = result && check_databus_read(values, builder);
185 if (!result) {
186 return report_fail("Failed databus read at row idx = ", idx);
187 }
188 // Note: EccOpQueueRelation is not checked here because it simply establishes that the ecc_op_wire
189 // polynomials contain copies of the conventional wire data in the ecc_op region (and are zero elsewhere) so
190 // there is nothing to check at the level of the builder.
191 }
192 if (!result) {
193 return report_fail("Failed at row idx = ", idx);
194 }
195 }
196
197 return result;
198};
199
200template <typename Relation> bool UltraCircuitChecker::check_relation(auto& values, auto& params)
201{
202 // Define zero initialized array to store the evaluation of each sub-relation
203 using SubrelationEvaluations = typename Relation::SumcheckArrayOfValuesOverSubrelations;
204 SubrelationEvaluations subrelation_evaluations;
205 for (auto& eval : subrelation_evaluations) {
206 eval = 0;
207 }
208
209 // Evaluate each subrelation in the relation
210 Relation::accumulate(subrelation_evaluations, values, params, /*scaling_factor=*/1);
211
212 // Ensure each subrelation evaluates to zero
213 for (auto& eval : subrelation_evaluations) {
214 if (eval != 0) {
215 return false;
216 }
217 }
218 return true;
219}
220
221bool UltraCircuitChecker::check_lookup(auto& values, auto& lookup_hash_table)
222{
223 // If this is a lookup gate, check the inputs are in the hash table containing all table entries
224 if (!values.q_lookup.is_zero()) {
225 return lookup_hash_table.contains({ values.w_l + values.q_r * values.w_l_shift,
226 values.w_r + values.q_m * values.w_r_shift,
227 values.w_o + values.q_c * values.w_o_shift,
228 values.q_o });
229 }
230 return true;
231};
232
233template <typename Builder> bool UltraCircuitChecker::check_databus_read(auto& values, Builder& builder)
234{
235 if (!values.q_busread.is_zero()) {
236 // Extract the {index, value} pair from the read gate inputs
237 auto raw_read_idx = static_cast<size_t>(uint256_t(values.w_r));
238 auto value = values.w_l;
239
240 // Determine the type of read based on selector values
241 bool is_calldata_read = (values.q_l == 1);
242 bool is_secondary_calldata_read = (values.q_r == 1);
243 bool is_return_data_read = (values.q_o == 1);
244 BB_ASSERT(is_calldata_read || is_secondary_calldata_read || is_return_data_read);
245
246 // Check that the claimed value is present in the calldata/return data at the corresponding index
247 FF bus_value;
248 if (is_calldata_read) {
249 auto calldata = builder.get_calldata();
250 bus_value = builder.get_variable(calldata[raw_read_idx]);
251 }
252 if (is_secondary_calldata_read) {
253 auto secondary_calldata = builder.get_secondary_calldata();
254 bus_value = builder.get_variable(secondary_calldata[raw_read_idx]);
255 }
256 if (is_return_data_read) {
257 auto return_data = builder.get_return_data();
258 bus_value = builder.get_variable(return_data[raw_read_idx]);
259 }
260 return (value == bus_value);
261 }
262 return true;
263};
264
266{
267 return tag_data.left_product == tag_data.right_product;
268};
269
270template <typename Builder>
272 Builder& builder, auto& block, auto& values, TagCheckData& tag_data, MemoryCheckData& memory_data, size_t idx)
273{
274 // Function to quickly update tag products and encountered variable set by index and value
275 auto update_tag_check_data = [&](const size_t variable_index, const FF& value) {
276 size_t real_index = builder.real_variable_index[variable_index];
277 // Check to ensure that we are not including a variable twice
278 if (tag_data.encountered_variables.contains(real_index)) {
279 return;
280 }
281 uint32_t tag_in = builder.real_variable_tags[real_index];
282 if (tag_in != DEFAULT_TAG) {
283 uint32_t tag_out = builder.tau().at(tag_in);
284 tag_data.left_product *= value + tag_data.gamma * FF(tag_in);
285 tag_data.right_product *= value + tag_data.gamma * FF(tag_out);
286 tag_data.encountered_variables.insert(real_index);
287 }
288 };
289
290 // A lambda function for computing a memory record term of the form w3 * eta_three + w2 * eta_two + w1 * eta
291 auto compute_memory_record_term =
292 [](const FF& w_1, const FF& w_2, const FF& w_3, const FF& eta, const FF& eta_two, FF& eta_three) {
293 return (w_3 * eta_three + w_2 * eta_two + w_1 * eta);
294 };
295
296 // Set wire values. Wire 4 is treated specially since it may contain memory records
297 values.w_l = builder.get_variable(block.w_l()[idx]);
298 values.w_r = builder.get_variable(block.w_r()[idx]);
299 values.w_o = builder.get_variable(block.w_o()[idx]);
300 // Note: memory_data contains indices into the block to which RAM/ROM gates were added so we need to check that
301 // we are indexing into the correct block before updating the w_4 value.
302 const bool is_ram_rom_block = (&block == &builder.blocks.memory);
303 if (is_ram_rom_block && memory_data.read_record_gates.contains(idx)) {
304 values.w_4 = compute_memory_record_term(
305 values.w_l, values.w_r, values.w_o, memory_data.eta, memory_data.eta_two, memory_data.eta_three);
306 } else if (is_ram_rom_block && memory_data.write_record_gates.contains(idx)) {
307 values.w_4 =
308 compute_memory_record_term(
309 values.w_l, values.w_r, values.w_o, memory_data.eta, memory_data.eta_two, memory_data.eta_three) +
310 FF::one();
311 } else {
312 values.w_4 = builder.get_variable(block.w_4()[idx]);
313 }
314
315 // Set shifted wire values. Again, wire 4 is treated specially. On final row, set shift values to zero
316 if (idx < block.size() - 1) {
317 values.w_l_shift = builder.get_variable(block.w_l()[idx + 1]);
318 values.w_r_shift = builder.get_variable(block.w_r()[idx + 1]);
319 values.w_o_shift = builder.get_variable(block.w_o()[idx + 1]);
320 if (is_ram_rom_block && memory_data.read_record_gates.contains(idx + 1)) {
321 values.w_4_shift = compute_memory_record_term(values.w_l_shift,
322 values.w_r_shift,
323 values.w_o_shift,
324 memory_data.eta,
325 memory_data.eta_two,
326 memory_data.eta_three);
327 } else if (is_ram_rom_block && memory_data.write_record_gates.contains(idx + 1)) {
328 values.w_4_shift = compute_memory_record_term(values.w_l_shift,
329 values.w_r_shift,
330 values.w_o_shift,
331 memory_data.eta,
332 memory_data.eta_two,
333 memory_data.eta_three) +
334 FF::one();
335 } else {
336 values.w_4_shift = builder.get_variable(block.w_4()[idx + 1]);
337 }
338 } else {
339 values.w_l_shift = 0;
340 values.w_r_shift = 0;
341 values.w_o_shift = 0;
342 values.w_4_shift = 0;
343 }
344
345 // Update tag check data
346 update_tag_check_data(block.w_l()[idx], values.w_l);
347 update_tag_check_data(block.w_r()[idx], values.w_r);
348 update_tag_check_data(block.w_o()[idx], values.w_o);
349 update_tag_check_data(block.w_4()[idx], values.w_4);
350
351 // Set selector values
352 values.q_m = block.q_m()[idx];
353 values.q_c = block.q_c()[idx];
354 values.q_l = block.q_1()[idx];
355 values.q_r = block.q_2()[idx];
356 values.q_o = block.q_3()[idx];
357 values.q_4 = block.q_4()[idx];
358 values.q_arith = block.q_arith()[idx];
359 values.q_delta_range = block.q_delta_range()[idx];
360 values.q_elliptic = block.q_elliptic()[idx];
361 values.q_memory = block.q_memory()[idx];
362 values.q_nnf = block.q_nnf()[idx];
363 values.q_lookup = block.q_lookup()[idx];
364 values.q_poseidon2_internal = block.q_poseidon2_internal()[idx];
365 values.q_poseidon2_external = block.q_poseidon2_external()[idx];
366 if constexpr (IsMegaBuilder<Builder>) {
367 values.q_busread = block.q_busread()[idx];
368 }
369}
370
371#ifdef ULTRA_FUZZ
372
384template <typename Builder> bool UltraCircuitChecker::relaxed_check_delta_range_relation(Builder& builder)
385{
386 std::unordered_map<uint32_t, uint64_t> range_tags;
387 for (const auto& list : builder.range_lists) {
388 range_tags[list.second.range_tag] = list.first;
389 }
390
391 // Unprocessed blocks check
392 for (uint32_t i = 0; i < builder.real_variable_tags.size(); i++) {
393 uint32_t tag = builder.real_variable_tags[i];
394 if (tag != 0 && range_tags.contains(tag)) {
395 uint256_t range = static_cast<uint256_t>(range_tags[tag]);
396 uint256_t value = static_cast<uint256_t>(builder.get_variable(i));
397 if (value > range) {
398#ifndef FUZZING_DISABLE_WARNINGS
399 info("Failed range constraint on variable with index = ", i, ": ", value, " > ", range);
400#endif
401 return false;
402 }
403 }
404 }
405
406 // Processed blocks check
407 auto block = builder.blocks.delta_range;
408 for (size_t idx = 0; idx < block.size(); idx++) {
409 if (block.q_delta_range()[idx] == 0) {
410 continue;
411 }
412 bb::fr w1 = builder.get_variable(block.w_l()[idx]);
413 bb::fr w2 = builder.get_variable(block.w_r()[idx]);
414 bb::fr w3 = builder.get_variable(block.w_o()[idx]);
415 bb::fr w4 = builder.get_variable(block.w_4()[idx]);
416 bb::fr w5 = idx == block.size() - 1 ? builder.get_variable(0) : builder.get_variable(block.w_l()[idx + 1]);
417
418 uint256_t delta = static_cast<uint256_t>(w2 - w1);
419 if (delta > 3) {
420#ifndef FUZZING_DISABLE_WARNINGS
421 info("Failed sort constraint relation at row idx = ", idx, " with delta1 = ", delta);
422 info(w1 - w2);
423#endif
424 return false;
425 }
426 delta = static_cast<uint256_t>(w3 - w2);
427 if (delta > 3) {
428#ifndef FUZZING_DISABLE_WARNINGS
429 info("Failed sort constraint relation at row idx = ", idx, " with delta2 = ", delta);
430#endif
431 return false;
432 }
433 delta = static_cast<uint256_t>(w4 - w3);
434 if (delta > 3) {
435#ifndef FUZZING_DISABLE_WARNINGS
436 info("Failed sort constraint at row idx = ", idx, " with delta3 = ", delta);
437#endif
438 return false;
439 }
440 delta = static_cast<uint256_t>(w5 - w4);
441 if (delta > 3) {
442#ifndef FUZZING_DISABLE_WARNINGS
443 info("Failed sort constraint at row idx = ", idx, " with delta4 = ", delta);
444#endif
445 return false;
446 }
447 }
448 return true;
449}
450
464template <typename Builder> bool UltraCircuitChecker::relaxed_check_memory_relation(Builder& builder)
465{
466 for (size_t i = 0; i < builder.rom_ram_logic.rom_arrays.size(); i++) {
467 auto rom_array = builder.rom_ram_logic.rom_arrays[i];
468
469 // check set and read ROM records
470 for (auto& rr : rom_array.records) {
471 uint32_t value_witness_1 = rr.value_column1_witness;
472 uint32_t value_witness_2 = rr.value_column2_witness;
473 uint32_t index = static_cast<uint32_t>(builder.get_variable(rr.index_witness));
474
475 uint32_t table_witness_1 = rom_array.state[index][0];
476 uint32_t table_witness_2 = rom_array.state[index][1];
477
478 if (builder.get_variable(value_witness_1) != builder.get_variable(table_witness_1)) {
479#ifndef FUZZING_DISABLE_WARNINGS
480 info("Failed SET/Read ROM[0] in table = ", i, " at idx = ", index);
481#endif
482 return false;
483 }
484 if (builder.get_variable(value_witness_2) != builder.get_variable(table_witness_2)) {
485#ifndef FUZZING_DISABLE_WARNINGS
486 info("Failed SET/Read ROM[1] in table = ", i, " at idx = ", index);
487#endif
488 return false;
489 }
490 }
491 }
492
493 for (size_t i = 0; i < builder.rom_ram_logic.ram_arrays.size(); i++) {
494 auto ram_array = builder.rom_ram_logic.ram_arrays[i];
495
496 std::vector<uint32_t> tmp_state(ram_array.state.size());
497
498 // Simulate the memory call trace
499 for (auto& rr : ram_array.records) {
500 uint32_t index = static_cast<uint32_t>(builder.get_variable(rr.index_witness));
501 uint32_t value_witness = rr.value_witness;
502 auto access_type = rr.access_type;
503
504 uint32_t table_witness = tmp_state[index];
505
506 switch (access_type) {
508 if (builder.get_variable(value_witness) != builder.get_variable(table_witness)) {
509#ifndef FUZZING_DISABLE_WARNINGS
510 info("Failed RAM read in table = ", i, " at idx = ", index);
511#endif
512 return false;
513 }
514 break;
516 tmp_state[index] = value_witness;
517 break;
518 default:
519 return false;
520 }
521 }
522
523 if (tmp_state != ram_array.state) {
524#ifndef FUZZING_DISABLE_WARNINGS
525 info("Failed RAM final state check at table = ", i);
526#endif
527 return false;
528 }
529 }
530 return true;
531}
532#endif
533
534// Template method instantiations for each check method
535template bool UltraCircuitChecker::check<UltraCircuitBuilder_<UltraExecutionTraceBlocks>>(
536 const UltraCircuitBuilder_<UltraExecutionTraceBlocks>& builder_in);
537template bool UltraCircuitChecker::check<MegaCircuitBuilder_<bb::fr>>(const MegaCircuitBuilder_<bb::fr>& builder_in);
538} // namespace bb
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
ArrayOfValues< FF, RelationImpl::SUBRELATION_PARTIAL_LENGTHS > SumcheckArrayOfValuesOverSubrelations
void finalize_circuit(const bool ensure_nonzero)
std::unordered_set< Key, HashFunction > LookupHashTable
static bool check_databus_read(auto &values, Builder &builder)
Check that the {index, value} pair contained in a databus read gate reflects the actual value present...
static bool check_relation(auto &values, auto &params)
Check that a given relation is satisfied for the provided inputs corresponding to a single row.
static bool check_tag_data(const TagCheckData &tag_data)
Check whether the left and right running tag products are equal.
static bool check_lookup(auto &values, auto &lookup_hash_table)
Check whether the values in a lookup gate are contained within a corresponding hash table.
static void populate_values(Builder &builder, auto &block, auto &values, TagCheckData &tag_data, MemoryCheckData &memory_data, size_t idx)
Populate the values required to check the correctness of a single "row" of the circuit.
static bool check(const Builder &builder_in)
Check the correctness of a circuit witness.
static Builder prepare_circuit(const Builder &builder_in)
Copy the builder and finalize it before checking its validity.
static bool check_block(Builder &builder, auto &block, TagCheckData &tag_data, MemoryCheckData &memory_data, LookupHashTable &lookup_hash_table)
Checks that the provided witness satisfies all gates contained in a single execution trace block.
A field element for each entity of the flavor. These entities represent the prover polynomials evalua...
#define info(...)
Definition log.hpp:93
AluTraceBuilder builder
Definition alu.test.cpp:124
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::vector< MemoryValue > calldata
Struct for managing memory record data for ensuring RAM/ROM correctness.
Struct for managing the running tag product data for ensuring tag correctness.
std::unordered_set< size_t > encountered_variables
static constexpr field one()