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