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