Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
rom_ram.test.cpp
Go to the documentation of this file.
1
4#include "ultra_honk.test.hpp"
5
6using namespace bb;
7
8#ifdef STARKNET_GARAGA_FLAVORS
9using FlavorTypes = testing::Types<UltraFlavor,
13 UltraStarknetFlavor,
14 UltraStarknetZKFlavor>;
15#else
16using FlavorTypes = testing::Types<UltraFlavor, UltraZKFlavor, UltraKeccakFlavor, UltraKeccakZKFlavor>;
17#endif
18
19template <typename Flavor> class MemoryTests_ : public UltraHonkTests<Flavor> {
20 public:
21 // helper types to check correctness of memory operations
22 // every time we do a read, we confirm the value is correct by using the corresponding "native" type below.
24 using NativeRamTable = std::vector<fr>;
39 auto& circuit_builder,
40 size_t array_length,
41 size_t num_pair_elts_in_ROM_table = 0, // the number of elements of our ROM table
42 // that will involve _pairs_ of numbers.
43 const size_t read_operations = 0,
44 bool final_arithmetic_gate_and_read = true) // toggles whether we apply a final arithmetic gate and read gate
45 {
46 BB_ASSERT_GTE(array_length, 1U, "The array length should be at least 1");
47 BB_ASSERT_LTE(num_pair_elts_in_ROM_table,
48 array_length,
49 "cannot set the number of 'pairs of elements to add to the ROM table' to be greater than the "
50 "length of the table");
51 // create a list of random variables, add them to the circuit, and record their witnesses.
52 // these will be the _initial_ elements of the ROM/RAM table. we have one extra to use the set-pair
53 // functionality.
54 std::vector<fr> variables(array_length + 1);
55 std::vector<uint32_t> variable_witnesses(array_length + 1);
56 for (auto [variable, witness] : zip_view(variables, variable_witnesses)) {
57 variable = fr::random_element();
58 witness = circuit_builder.add_variable(variable);
59 }
60
61 // build our "native" ROM table to check our operations
62 NativeRomTable native_rom_table(array_length);
63 const size_t num_single_elts_in_ROM_table = array_length - num_pair_elts_in_ROM_table;
64
65 // Single-value and pair-value entries live on _separate_ ROM arrays: single uses the LogUp scheme and
66 // pair uses the sorted-trace scheme; the two cannot share an array. We still keep a single
67 // `native_rom_table` for the test oracle. Convert from logical index to per-array index by subtracting
68 // num_single_elts_in_ROM_table for the pair side.
69 size_t rom_single_id = circuit_builder.create_ROM_array(num_single_elts_in_ROM_table);
70 size_t rom_pair_id = circuit_builder.create_ROM_array(num_pair_elts_in_ROM_table);
71
72 // Per-array index witnesses so reads pass the right witness for each array's local index space.
73 std::vector<uint32_t> single_index_witnesses(num_single_elts_in_ROM_table);
74 for (size_t i = 0; i < num_single_elts_in_ROM_table; ++i) {
75 single_index_witnesses[i] = circuit_builder.put_constant_variable(static_cast<uint64_t>(i));
76 }
77 std::vector<uint32_t> pair_index_witnesses(num_pair_elts_in_ROM_table);
78 for (size_t i = 0; i < num_pair_elts_in_ROM_table; ++i) {
79 pair_index_witnesses[i] = circuit_builder.put_constant_variable(static_cast<uint64_t>(i));
80 }
81 // single ROM elements
82 for (size_t i = 0; i < num_single_elts_in_ROM_table; ++i) {
83 circuit_builder.set_ROM_element(rom_single_id, i, variable_witnesses[i]);
84 native_rom_table[i] = std::array{ variables[i], fr::zero() };
85 }
86 // pair ROM elements (indexed locally on the pair array)
87 for (size_t i = num_single_elts_in_ROM_table; i < array_length; ++i) {
88 const size_t pair_local = i - num_single_elts_in_ROM_table;
89 circuit_builder.set_ROM_element_pair(
90 rom_pair_id, pair_local, std::array{ variable_witnesses[i], variable_witnesses[i + 1] });
91 native_rom_table[i] = std::array{ variables[i], variables[i + 1] };
92 }
93 // perform some random read operations (which add rows to the execution trace) and check "natively" that the
94 // reads are correct. note that if we are reading a row of the ROM table that had a _pair_ being entered in,
95 // then we _must_ call `read_ROM_array_pair`.
96 for (size_t i = 0; i < read_operations; ++i) {
97 uint32_t random_read_index = static_cast<uint32_t>(
98 engine.get_random_uint32() % array_length); // a random index to read from in my ROM array.
99
100 if (random_read_index < num_single_elts_in_ROM_table) {
101 uint32_t read_witness_index =
102 circuit_builder.read_ROM_array(rom_single_id, single_index_witnesses[random_read_index]);
103 // check fidelity of the memory read
104 auto actually_read_value = circuit_builder.get_variable(read_witness_index);
105 auto expected_value = native_rom_table[random_read_index][0];
106 BB_ASSERT_EQ(actually_read_value, expected_value);
107 } else {
108 const size_t pair_local = random_read_index - num_single_elts_in_ROM_table;
109 auto [read_witness_index_1, read_witness_index_2] =
110 circuit_builder.read_ROM_array_pair(rom_pair_id, pair_index_witnesses[pair_local]);
111 // check fidelity of the pair memory read
112 std::array<fr, 2> actually_read_values = { circuit_builder.get_variable(read_witness_index_1),
113 circuit_builder.get_variable(read_witness_index_2) };
114 auto expected_values = native_rom_table[random_read_index];
115 BB_ASSERT_EQ(actually_read_values[0], expected_values[0]);
116 BB_ASSERT_EQ(actually_read_values[1], expected_values[1]);
117 }
118 }
119 if (final_arithmetic_gate_and_read) {
120 // Final gate checks: construct a `big_add_gate` with random values from the ROM table, then perform another
121 // read (which adds rows to our execution trace). This checks that nothing unexpected happens when we
122 // include basic arithmetic gates.
123
124 // build three random indices, store their witnesses for the final check.
125 // in the case when there are _pairs_ of element in the ROM table row, we only use the _first_ entry for our
126 // gate check.
127 std::array<uint32_t, 3> random_indices_to_check_computation;
128 std::array<fr, 3> native_fr_elts_to_check_computation;
129 for (size_t i = 0; i < 3; i++) {
130 uint32_t random_index_to_check_computation =
131 static_cast<uint32_t>(engine.get_random_uint32() % array_length);
132 random_indices_to_check_computation[i] = random_index_to_check_computation;
133 native_fr_elts_to_check_computation[i] =
134 native_rom_table[random_index_to_check_computation]
135 [0]; // note that we only use the first entry of
136 // `native_rom_table[random_index_to_check_computation]`.
137 }
138
139 // Perform the reads at the random indices, handling single vs pair reads
140 std::array<uint32_t, 3> final_check_read_witnesses;
141 for (size_t i = 0; i < 3; i++) {
142 const auto random_idx = random_indices_to_check_computation[i];
143
144 if (random_idx < num_single_elts_in_ROM_table) {
145 final_check_read_witnesses[i] =
146 circuit_builder.read_ROM_array(rom_single_id, single_index_witnesses[random_idx]);
147 } else {
148 // For pairs, we only use the first element in the final check
149 const size_t pair_local = random_idx - num_single_elts_in_ROM_table;
150 auto [first, _] =
151 circuit_builder.read_ROM_array_pair(rom_pair_id, pair_index_witnesses[pair_local]);
152 final_check_read_witnesses[i] = first;
153 }
154 }
155
156 // add the `big_add_gate`
157 const fr d_value = std::accumulate(
158 native_fr_elts_to_check_computation.begin(), native_fr_elts_to_check_computation.end(), fr::zero());
159 uint32_t d_idx = circuit_builder.add_variable(d_value);
160 circuit_builder.create_big_add_gate({
161 final_check_read_witnesses[0],
162 final_check_read_witnesses[1],
163 final_check_read_witnesses[2],
164 d_idx,
165 1,
166 1,
167 1,
168 -1,
169 0,
170 });
171 // add a read row, to make sure we can intersperse the operations, as expected.
172 if (num_single_elts_in_ROM_table > 0) {
173 uint32_t random_read_index = static_cast<uint32_t>(
175 num_single_elts_in_ROM_table); // a random index to read from in my ROM array. we read from
176 // the part of the table that only has _single_ ROM entries.
177 circuit_builder.read_ROM_array(rom_single_id, single_index_witnesses[random_read_index]);
178 } else {
179 uint32_t random_read_index = static_cast<uint32_t>(
181 num_pair_elts_in_ROM_table); // a random index to read from in my ROM array. we read from
182 // the part of the table that only has _single_ ROM entries.
183 circuit_builder.read_ROM_array_pair(rom_pair_id, pair_index_witnesses[random_read_index]);
184 }
185 }
186 }
187
188 static void build_ROM_table_length_zero(auto& circuit_builder) { circuit_builder.create_ROM_array(0); }
189 static void build_ROM_table_with_uninitialized_values(auto& circuit_builder, size_t array_length)
190 {
191 circuit_builder.create_ROM_array(array_length);
192 }
193 static void build_failing_ROM_table(auto& circuit_builder, size_t array_length, ROMFailureType rom_failure_type)
194 {
196 auto rom_id = circuit_builder.create_ROM_array(array_length);
197 auto zero_idx = circuit_builder.zero_idx();
198 auto random_num = fr::random_element();
199 auto random_variable_idx = circuit_builder.add_variable(random_num);
200 switch (rom_failure_type) {
201 // One element is doubly initialized. This uses the pair API because the two schemes reject double-init
202 // in different places: the sorted-trace (pair) scheme rejects it in-circuit via its adjacent-row
203 // consistency check, yielding an unsatisfiable circuit that prove_and_verify can detect; the
204 // single-value LogUp scheme instead rejects it at construction time (set_ROM_element asserts the cell
205 // is uninitialized, see rom_ram_logic.cpp), so its rejection cannot be expressed as a failing circuit
206 // here, especially with asserts disabled above. Hence the in-circuit failure path is tested via pairs.
208 for (size_t i = 0; i < array_length; ++i) {
209 circuit_builder.set_ROM_element_pair(rom_id, i, std::array{ zero_idx, zero_idx });
210 }
211 circuit_builder.set_ROM_element_pair(rom_id,
212 engine.get_random_uint32() % array_length,
213 std::array{ random_variable_idx, random_variable_idx });
214 break;
215 }
216 // we try to read a single element at a ROM entry that contains a _pair_ of values.
218 for (size_t i = 0; i < array_length; ++i) {
219 circuit_builder.set_ROM_element_pair(rom_id, i, std::array{ random_variable_idx, random_variable_idx });
220 }
221 // read the first element
222 circuit_builder.read_ROM_array(rom_id, zero_idx);
223 break;
224 }
225 };
226 }
227 static void build_random_RAM_table(auto& circuit_builder,
228 size_t array_length,
229 const size_t read_write_operations = 0,
230 bool final_arithmetic_gate_and_read = true)
231 {
232
233 // create a list of random variables, add them to the circuit, and record their witnesses.
234 // these will be the _initial_ elements of the RAM table.
235 std::vector<fr> variables(array_length);
236 std::vector<uint32_t> variable_witnesses(array_length);
237 for (auto [variable, witness] : zip_view(variables, variable_witnesses)) {
238 variable = fr::random_element();
239 witness = circuit_builder.add_variable(variable);
240 }
241
242 // array pointing to the witness indicies whose associated real variable is `i`.
243 // this is used for testing
244 std::vector<uint32_t> index_witness_indices(array_length);
245 for (size_t i = 0; i < array_length; ++i) {
246 index_witness_indices[i] = circuit_builder.put_constant_variable(static_cast<uint64_t>(i));
247 }
248 NativeRamTable native_ram_table(array_length);
249 size_t ram_table_id = circuit_builder.create_RAM_array(array_length);
250 // witness indices of the indicies of the array, as we will have to perform "random write operations"
251 for (size_t i = 0; i < array_length; ++i) {
252 circuit_builder.init_RAM_element(ram_table_id, i, variable_witnesses[i]);
253 native_ram_table[i] = variables[i];
254 }
255
256 // perform some random read and write operations, which add rows to the execution trace.
257 for (size_t i = 0; i < read_write_operations; ++i) {
258 // write ops
259 size_t random_write_index = static_cast<size_t>(engine.get_random_uint32() % array_length);
260 fr random_element = fr::random_element();
261 uint32_t write_variable_witness = circuit_builder.add_variable(random_element);
262 native_ram_table[random_write_index] = random_element;
263 circuit_builder.write_RAM_array(
264 ram_table_id, index_witness_indices[random_write_index], write_variable_witness);
265 // read ops, with a "native" check that the values are correct.
266 size_t random_read_index = static_cast<size_t>(engine.get_random_uint32() % array_length);
267 uint32_t read_witness =
268 circuit_builder.read_RAM_array(ram_table_id, index_witness_indices[random_read_index]);
269 auto read_value = circuit_builder.get_variable(read_witness);
270 auto expected_value = native_ram_table[random_read_index];
271 BB_ASSERT_EQ(read_value, expected_value, "the value the RAM table read was not the expected value");
272 }
273 if (final_arithmetic_gate_and_read) {
274 // Final gate checks: construct a `big_add_gate` with values from the RAM table, then perform another
275 // read (which adds rows to our execution trace). This checks that nothing unexpected happens when we
276 // include basic arithmetic gates.
277
278 // build three random indices, store their witnesses for the final check.
279 std::array<uint32_t, 3> random_index_witnesses_to_check_computation;
280 std::array<fr, 3> native_fr_elts_to_check_computation;
281 for (size_t i = 0; i < 3; i++) {
282 uint32_t random_index_to_check_computation =
283 static_cast<uint32_t>(engine.get_random_uint32() % array_length);
284 random_index_witnesses_to_check_computation[i] =
285 index_witness_indices[random_index_to_check_computation];
286 native_fr_elts_to_check_computation[i] = native_ram_table[random_index_to_check_computation];
287 }
288 // Perform the ops at the random indices, handling single vs pair reads
289 std::array<uint32_t, 3> final_check_read_witnesses;
290 for (size_t i = 0; i < 3; i++) {
291 const auto random_idx_witness = random_index_witnesses_to_check_computation[i];
292 final_check_read_witnesses[i] = circuit_builder.read_RAM_array(ram_table_id, random_idx_witness);
293 }
294
295 // add the `big_add_gate`
296 const fr d_value = std::accumulate(
297 native_fr_elts_to_check_computation.begin(), native_fr_elts_to_check_computation.end(), fr::zero());
298 uint32_t d_idx = circuit_builder.add_variable(d_value);
299 circuit_builder.create_big_add_gate({
300 final_check_read_witnesses[0],
301 final_check_read_witnesses[1],
302 final_check_read_witnesses[2],
303 d_idx,
304 1,
305 1,
306 1,
307 -1,
308 0,
309 });
310 // add a read row, to make sure we can intersperse the operations, as expected.
311 uint32_t random_read_index =
313 static_cast<uint32_t>(array_length); // a random index to read from in my ROM array.
314 circuit_builder.read_RAM_array(ram_table_id, index_witness_indices[random_read_index]);
315 }
316 }
317
318 static void build_RAM_table_length_zero(auto& circuit_builder) { circuit_builder.create_RAM_array(0); }
319};
321
323{
324 using Flavor = TypeParam;
325 using MemoryTests = MemoryTests_<Flavor>;
326 auto circuit_builder = UltraCircuitBuilder();
327 MemoryTests::build_ROM_table_length_zero(circuit_builder);
328
329 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(circuit_builder);
330 TestFixture::prove_and_verify(circuit_builder, /*expected_result=*/true);
331}
333{
334 using Flavor = TypeParam;
335 using MemoryTests = MemoryTests_<Flavor>;
336 auto circuit_builder = UltraCircuitBuilder();
337 size_t array_size = 1;
338 size_t num_pair_elts = 0;
339 size_t num_reads = 0;
340 bool final_arithmetic_gate_and_read = false;
341 MemoryTests::build_random_ROM_table(
342 circuit_builder, array_size, num_pair_elts, num_reads, final_arithmetic_gate_and_read);
343
344 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(circuit_builder);
345 TestFixture::prove_and_verify(circuit_builder, /*expected_result=*/true);
346}
347TYPED_TEST(UltraHonkTests, RomTinyRepeated)
348{
349 using Flavor = TypeParam;
350 using MemoryTests = MemoryTests_<Flavor>;
351 auto circuit_builder = UltraCircuitBuilder();
352 size_t array_size = 2;
353 size_t num_pair_elts = 1;
354 size_t num_reads = 5;
355 // Build multiple ROM tables to test repeated table creation
356 constexpr size_t num_tables = 5;
357 for (size_t i = 0; i < num_tables; ++i) {
358 MemoryTests::build_random_ROM_table(circuit_builder, array_size, num_pair_elts, num_reads);
359 }
360
361 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(circuit_builder);
362 TestFixture::prove_and_verify(circuit_builder, /*expected_result=*/true);
363}
364
366{
367 using Flavor = TypeParam;
368 using MemoryTests = MemoryTests_<Flavor>;
369 auto circuit_builder = UltraCircuitBuilder();
370 MemoryTests::build_RAM_table_length_zero(circuit_builder);
371 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(circuit_builder);
372 TestFixture::prove_and_verify(circuit_builder, /*expected_result=*/true);
373}
375{
376 using Flavor = TypeParam;
377 using MemoryTests = MemoryTests_<Flavor>;
378 auto circuit_builder = UltraCircuitBuilder();
379 MemoryTests::build_RAM_table_length_zero(circuit_builder);
380 size_t array_size = 1;
381 size_t read_write_ops = 5;
382 bool final_arithmetic_gate_and_read = false;
383 MemoryTests::build_random_RAM_table(circuit_builder, array_size, read_write_ops, final_arithmetic_gate_and_read);
384 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(circuit_builder);
385 TestFixture::prove_and_verify(circuit_builder, /*expected_result=*/true);
386}
387
389{
390 using Flavor = TypeParam;
391 using MemoryTests = MemoryTests_<Flavor>;
392 auto circuit_builder = UltraCircuitBuilder();
393 size_t array_size = 15;
394 size_t num_pair_elts = 5;
395 size_t num_reads = 5;
396 size_t read_write_ops = 5;
397 constexpr size_t num_tables = 5;
398 for (size_t i = 0; i < num_tables; ++i) {
399 MemoryTests::build_random_RAM_table(circuit_builder, array_size, read_write_ops);
400 MemoryTests::build_random_ROM_table(circuit_builder, array_size, num_pair_elts, num_reads);
401 }
402 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(circuit_builder);
403 TestFixture::prove_and_verify(circuit_builder, /*expected_result=*/true);
404}
405
406TYPED_TEST(UltraHonkTests, RomFailureDoubleInit)
407{
408 using Flavor = TypeParam;
409 using MemoryTests = MemoryTests_<Flavor>;
410 auto circuit_builder = UltraCircuitBuilder();
411 size_t array_length = 5;
412 auto rom_failure_type = MemoryTests::ROMFailureType::DoubleInit;
413 MemoryTests::build_failing_ROM_table(circuit_builder, array_length, rom_failure_type);
414
415 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(circuit_builder);
416 TestFixture::prove_and_verify(circuit_builder, /*expected_result=*/false);
417}
418
419TYPED_TEST(UltraHonkTests, RomFailureSingleReadAtPair)
420{
421 using Flavor = TypeParam;
422 using MemoryTests = MemoryTests_<Flavor>;
423 auto circuit_builder = UltraCircuitBuilder();
424 size_t array_length = 5;
425 auto rom_failure_type = MemoryTests::ROMFailureType::SingleReadAtPair;
426 MemoryTests::build_failing_ROM_table(circuit_builder, array_length, rom_failure_type);
427
428 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(circuit_builder);
429 TestFixture::prove_and_verify(circuit_builder, /*expected_result=*/false);
430}
431
432// Test malicious initialization value in ROM
433TYPED_TEST(UltraHonkTests, RomMaliciousInitValue)
434{
435 using Flavor = TypeParam;
436 using FF = typename Flavor::FF;
438
439 // Create a simple ROM with one malicious initialization value
440 size_t rom_id = injector.builder.create_ROM_array(5);
441
442 // This witness has value 42 in good proof, 666 in bad proof
443 auto malicious_witness = injector.add_malicious_variable(FF(42), FF(666));
444
445 // Initialize ROM with the malicious witness
446 injector.builder.set_ROM_element(rom_id, 0, malicious_witness);
447
448 // Initialize remaining elements with arbitrary values
449 for (size_t i = 1; i < 5; ++i) {
450 auto good_witness = injector.builder.add_variable(FF::random_element());
451 injector.builder.set_ROM_element(rom_id, i, good_witness);
452 }
453
454 // Read the malicious element to create constraints
455 auto index = injector.builder.put_constant_variable(0);
456 injector.builder.read_ROM_array(rom_id, index);
457
458 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(injector.builder);
459
460 // Run CircuitChecker; expect failure in Memory relation for malicious witness
461 EXPECT_TRUE(CircuitChecker::check(injector.builder)); // good builder passes
462 auto bad_builder = injector.create_builder_with_malicious_witnesses();
463 EXPECT_FALSE(CircuitChecker::check(bad_builder)); // bad builder fails (will print "Failed Memory relation")
464
465 // Run full protocol
466 auto [good_instance, bad_instance] = injector.create_instances();
467 TestFixture::prove_and_verify(good_instance, /*expected_result=*/true);
468 TestFixture::prove_and_verify(bad_instance, /*expected_result=*/false);
469}
470
471// Field-wrap sorted-chain attack: before the fix, process_ROM/RAM_array used add_variable(FF(0))
472// for the first sorted gate's index_witness, which is an unconstrained free witness. The
473// index-delta sub-relation (w1 - w1_shift)^2 + (w1 - w1_shift) = 0 has roots {0, -1} over the
474// field, so a sorted chain starting at p-1 satisfies the delta check when transitioning to 0, and
475// a malicious prover could read/write fake values at index p-1. The fix uses zero_idx(), whose
476// fix_witness arithmetic gate (w1 * 1 + 0 = 0) catches any attempted mutation to p-1.
477
478// Test that a malicious prover cannot start the ROM sorted chain at p-1. The defense lives in the
479// sorted-trace scheme used by the pair-value ROM API; the single-value LogUp scheme has no sorted chain and
480// is not susceptible to this attack vector, so the test exercises the pair API specifically.
481TYPED_TEST(UltraHonkTests, RomMaliciousFieldWrap)
482{
483 using Flavor = TypeParam;
484 using Builder = typename Flavor::CircuitBuilder;
485 using FF = typename Flavor::FF;
486
487 Builder circuit_builder;
488
489 // Build a small ROM: pairs (1, 0), (2, 0), (3, 0) at indices [0, 1, 2]. Uses the pair API so we go
490 // through the sorted-trace scheme; the second pair entry is unused by the test logic below.
491 const uint32_t zero_idx = circuit_builder.zero_idx();
492 size_t rom_id = circuit_builder.create_ROM_array(3);
493 circuit_builder.set_ROM_element_pair(rom_id, 0, std::array{ circuit_builder.add_variable(FF(1)), zero_idx });
494 circuit_builder.set_ROM_element_pair(rom_id, 1, std::array{ circuit_builder.add_variable(FF(2)), zero_idx });
495 circuit_builder.set_ROM_element_pair(rom_id, 2, std::array{ circuit_builder.add_variable(FF(3)), zero_idx });
496
497 // Read at index 0 via a variable index witness so an attacker can later change it.
498 uint32_t idx_witness = circuit_builder.add_variable(FF(0));
499 auto [result_witness, _unused] = circuit_builder.read_ROM_array_pair(rom_id, idx_witness);
500
501 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(circuit_builder);
502
503 // Explicitly finalize so process_ROM_array runs and we can inspect the sorted gates.
504 circuit_builder.finalize_circuit();
505
506 // Locate the first sorted ROM consistency check gate (q_memory=1, q_1=1, q_2=1, q_m=0).
507 auto& mem = circuit_builder.blocks.memory;
508 size_t sorted_0 = std::numeric_limits<size_t>::max();
509 for (size_t i = 0; i < mem.size(); ++i) {
510 if (mem.gate_selector_for(GateKind::Memory)[i] == 1 && mem.q_1()[i] == 1 && mem.q_2()[i] == 1 &&
511 mem.q_m()[i] == 0) {
512 sorted_0 = i;
513 break;
514 }
515 }
516 ASSERT_NE(sorted_0, std::numeric_limits<size_t>::max()) << "no sorted ROM consistency gate found";
517
518 // After the fix the first sorted gate must use zero_idx() as its index witness.
519 uint32_t sorted_0_idx_w = mem.w_l()[sorted_0];
520 uint32_t sorted_0_val_w = mem.w_r()[sorted_0];
521 EXPECT_EQ(sorted_0_idx_w, circuit_builder.zero_idx());
522
523 // Build the malicious copy: forge a read at index p-1 returning value 999.
524 // Sorted chain would become: [(p-1,999), (0,1), (1,2), (2,3), dummy(3)].
525 // Mutating sorted_0's index_witness (== zero_idx()) to p-1 violates the fix_witness
526 // arithmetic gate and must be rejected.
527 Builder bad_builder = circuit_builder;
528 auto& vars = const_cast<std::vector<FF>&>(bad_builder.get_variables());
529 const FF p_minus_1 = -FF(1);
530 vars[bad_builder.real_variable_index[sorted_0_idx_w]] = p_minus_1;
531 vars[bad_builder.real_variable_index[sorted_0_val_w]] = FF(999);
532 vars[bad_builder.real_variable_index[idx_witness]] = p_minus_1;
533 vars[bad_builder.real_variable_index[result_witness]] = FF(999);
534
535 // Run CircuitChecker: expected error in the arithmetic sub-relation from zero_idx's fix_witness.
536 EXPECT_TRUE(CircuitChecker::check(circuit_builder));
537 EXPECT_FALSE(CircuitChecker::check(bad_builder));
538
539 // Run full protocol.
540 TestFixture::prove_and_verify(circuit_builder, /*expected_result=*/true);
541 TestFixture::prove_and_verify(bad_builder, /*expected_result=*/false);
542}
543
544// Cross-array read binding: the single-value ROM LogUp sum runs over the whole trace, so the fingerprint
545// includes the array id (carried in q_c) to keep each read matched to a table entry of its own array. This
546// test exercises that binding by forging a cross-array read: array A holds 100 at index 0, array B holds 200
547// at index 0; the malicious witness makes B's read return A's value (100) and rebalances the per-array
548// multiplicities (m_A: 1 -> 2, m_B: 1 -> 0) so that an id-blind sum would still vanish. The array id in the
549// fingerprint gives A's and B's rows distinct denominators, so the forged witness is rejected.
550TYPED_TEST(UltraHonkTests, RomMaliciousCrossArrayRead)
551{
552 using Flavor = TypeParam;
553 using Builder = typename Flavor::CircuitBuilder;
554 using FF = typename Flavor::FF;
555
556 Builder circuit_builder;
557
558 // Two single-value ROM arrays, same index, different values.
559 size_t rom_a = circuit_builder.create_ROM_array(1);
560 size_t rom_b = circuit_builder.create_ROM_array(1);
561 circuit_builder.set_ROM_element(rom_a, 0, circuit_builder.add_variable(FF(100)));
562 circuit_builder.set_ROM_element(rom_b, 0, circuit_builder.add_variable(FF(200)));
563
564 // Read each array once at index 0.
565 circuit_builder.read_ROM_array(rom_a, circuit_builder.add_variable(FF(0)));
566 const uint32_t b_read_value_witness = circuit_builder.read_ROM_array(rom_b, circuit_builder.add_variable(FF(0)));
567
568 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(circuit_builder);
569 circuit_builder.finalize_circuit();
570
571 // Locate, in the memory block, the multiplicity (w_o) witnesses of each array's table row. Table rows
572 // carry (q_memory=1, q_2=1, q_1=0); we tell A's row from B's by its stored value.
573 auto& mem = circuit_builder.blocks.memory;
574 uint32_t m_a_witness = std::numeric_limits<uint32_t>::max();
575 uint32_t m_b_witness = std::numeric_limits<uint32_t>::max();
576 for (size_t i = 0; i < mem.size(); ++i) {
577 const bool is_logup_table =
578 mem.gate_selector_for(GateKind::Memory)[i] == 1 && mem.q_1()[i] == 0 && mem.q_2()[i] == 1;
579 if (!is_logup_table) {
580 continue;
581 }
582 const FF value = circuit_builder.get_variable(mem.w_r()[i]);
583 if (value == FF(100)) {
584 m_a_witness = mem.w_o()[i];
585 } else if (value == FF(200)) {
586 m_b_witness = mem.w_o()[i];
587 }
588 }
589 ASSERT_NE(m_a_witness, std::numeric_limits<uint32_t>::max()) << "array A table row not found";
590 ASSERT_NE(m_b_witness, std::numeric_limits<uint32_t>::max()) << "array B table row not found";
591
592 // Forge the witness: B's read returns A's value (200 -> 100), with multiplicities rebalanced so the
593 // global LogUp sum is still zero (A now absorbs two reads of value 100, B absorbs none).
594 Builder bad_builder = circuit_builder;
595 auto& vars = const_cast<std::vector<FF>&>(bad_builder.get_variables());
596 vars[bad_builder.real_variable_index[b_read_value_witness]] = FF(100);
597 vars[bad_builder.real_variable_index[m_a_witness]] = FF(2);
598 vars[bad_builder.real_variable_index[m_b_witness]] = FF(0);
599
600 EXPECT_TRUE(CircuitChecker::check(circuit_builder));
601 EXPECT_FALSE(CircuitChecker::check(bad_builder));
602
603 TestFixture::prove_and_verify(circuit_builder, /*expected_result=*/true);
604 TestFixture::prove_and_verify(bad_builder, /*expected_result=*/false);
605}
606
607// Forged read value: a read must return the value the table holds at its index. Here array A holds 100 at
608// index 0 and 200 at index 1; the malicious witness makes the read at index 0 return 200 (a value that is in
609// the table, but at a different index). Because the index is part of the fingerprint, (index 0, value 200)
610// matches no table entry, so no choice of multiplicities can rebalance the global sum.
611TYPED_TEST(UltraHonkTests, RomMaliciousForgedReadValue)
612{
613 using Flavor = TypeParam;
614 using Builder = typename Flavor::CircuitBuilder;
615 using FF = typename Flavor::FF;
616
617 Builder circuit_builder;
618
619 size_t rom_id = circuit_builder.create_ROM_array(2);
620 circuit_builder.set_ROM_element(rom_id, 0, circuit_builder.add_variable(FF(100)));
621 circuit_builder.set_ROM_element(rom_id, 1, circuit_builder.add_variable(FF(200)));
622 const uint32_t read_value_witness = circuit_builder.read_ROM_array(rom_id, circuit_builder.add_variable(FF(0)));
623
624 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(circuit_builder);
625 circuit_builder.finalize_circuit();
626
627 // Forge the index-0 read to return 200 (the value stored at index 1).
628 Builder bad_builder = circuit_builder;
629 auto& vars = const_cast<std::vector<FF>&>(bad_builder.get_variables());
630 vars[bad_builder.real_variable_index[read_value_witness]] = FF(200);
631
632 EXPECT_TRUE(CircuitChecker::check(circuit_builder));
633 EXPECT_FALSE(CircuitChecker::check(bad_builder));
634
635 TestFixture::prove_and_verify(circuit_builder, /*expected_result=*/true);
636 TestFixture::prove_and_verify(bad_builder, /*expected_result=*/false);
637}
638
639// Tampered multiplicity: the per-index read count lives in a single-use w_o witness, pinned only by the
640// global LogUp sum. Understating it (here m_0: 1 -> 0, claiming the read never happened) leaves the read's
641// term uncancelled, so the sum no longer vanishes.
642TYPED_TEST(UltraHonkTests, RomMaliciousMultiplicity)
643{
644 using Flavor = TypeParam;
645 using Builder = typename Flavor::CircuitBuilder;
646 using FF = typename Flavor::FF;
647
648 Builder circuit_builder;
649
650 size_t rom_id = circuit_builder.create_ROM_array(1);
651 circuit_builder.set_ROM_element(rom_id, 0, circuit_builder.add_variable(FF(100)));
652 circuit_builder.read_ROM_array(rom_id, circuit_builder.add_variable(FF(0)));
653
654 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(circuit_builder);
655 circuit_builder.finalize_circuit();
656
657 // Find the table row's multiplicity (w_o) witness: q_memory=1, q_2=1, q_1=0.
658 auto& mem = circuit_builder.blocks.memory;
659 uint32_t m_witness = std::numeric_limits<uint32_t>::max();
660 for (size_t i = 0; i < mem.size(); ++i) {
661 if (mem.gate_selector_for(GateKind::Memory)[i] == 1 && mem.q_1()[i] == 0 && mem.q_2()[i] == 1) {
662 m_witness = mem.w_o()[i];
663 break;
664 }
665 }
666 ASSERT_NE(m_witness, std::numeric_limits<uint32_t>::max()) << "ROM-LogUp table row not found";
667
668 Builder bad_builder = circuit_builder;
669 auto& vars = const_cast<std::vector<FF>&>(bad_builder.get_variables());
670 vars[bad_builder.real_variable_index[m_witness]] = FF(0);
671
672 EXPECT_TRUE(CircuitChecker::check(circuit_builder));
673 EXPECT_FALSE(CircuitChecker::check(bad_builder));
674
675 TestFixture::prove_and_verify(circuit_builder, /*expected_result=*/true);
676 TestFixture::prove_and_verify(bad_builder, /*expected_result=*/false);
677}
678
679// Init-only array (no reads): all multiplicities are zero so the LogUp sum vanishes trivially, but the
680// per-row inverse subrelation is still active on every table row. Exercises the empty-read edge case.
681TYPED_TEST(UltraHonkTests, RomLogupInitOnly)
682{
683 using Flavor = TypeParam;
684 using FF = typename Flavor::FF;
685 auto circuit_builder = UltraCircuitBuilder();
686
687 size_t rom_id = circuit_builder.create_ROM_array(3);
688 for (size_t i = 0; i < 3; ++i) {
689 circuit_builder.set_ROM_element(rom_id, i, circuit_builder.add_variable(FF(10 + i)));
690 }
691
692 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(circuit_builder);
693 TestFixture::prove_and_verify(circuit_builder, /*expected_result=*/true);
694}
695
696// Double-init guard: the single-value LogUp scheme requires at most one table row per (array, index) key and
697// does not enforce this in-circuit, so set_ROM_element asserts each cell is uninitialized (see
698// rom_ram_logic.cpp). Initializing the same index twice must be rejected at construction time.
699TYPED_TEST(UltraHonkTests, RomSingleValueDoubleInitThrows)
700{
701 using Flavor = TypeParam;
702 using FF = typename Flavor::FF;
703 auto circuit_builder = UltraCircuitBuilder();
704
705 size_t rom_id = circuit_builder.create_ROM_array(1);
706 circuit_builder.set_ROM_element(rom_id, 0, circuit_builder.add_variable(FF(100)));
707
708 EXPECT_THROW_WITH_MESSAGE(circuit_builder.set_ROM_element(rom_id, 0, circuit_builder.add_variable(FF(200))),
709 "UNINITIALIZED_MEMORY_RECORD");
710}
711
712// Test that a malicious prover cannot start the RAM sorted chain at p-1.
713TYPED_TEST(UltraHonkTests, RamMaliciousFieldWrap)
714{
715 using Flavor = TypeParam;
716 using Builder = typename Flavor::CircuitBuilder;
717 using FF = typename Flavor::FF;
718
719 Builder circuit_builder;
720
721 // Build a small RAM: initialise [1, 2, 3].
722 size_t ram_id = circuit_builder.create_RAM_array(3);
723 circuit_builder.init_RAM_element(ram_id, 0, circuit_builder.add_variable(FF(1)));
724 circuit_builder.init_RAM_element(ram_id, 1, circuit_builder.add_variable(FF(2)));
725 circuit_builder.init_RAM_element(ram_id, 2, circuit_builder.add_variable(FF(3)));
726
727 // Write using a variable index so an attacker can later change it to p-1.
728 uint32_t idx_witness = circuit_builder.add_variable(FF(0));
729 uint32_t write_val_w = circuit_builder.add_variable(FF(999));
730 circuit_builder.write_RAM_array(ram_id, idx_witness, write_val_w);
731
732 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(circuit_builder);
733
734 // Explicitly finalize so process_RAM_array runs and we can inspect the sorted gates.
735 circuit_builder.finalize_circuit();
736
737 // Locate the first sorted RAM consistency check gate (q_memory=1, q_3=1, q_1=q_2=q_m=0).
738 auto& mem = circuit_builder.blocks.memory;
739 size_t sorted_0 = std::numeric_limits<size_t>::max();
740 for (size_t i = 0; i < mem.size(); ++i) {
741 if (mem.gate_selector_for(GateKind::Memory)[i] == 1 && mem.q_3()[i] == 1 && mem.q_1()[i] == 0 &&
742 mem.q_2()[i] == 0 && mem.q_m()[i] == 0) {
743 sorted_0 = i;
744 break;
745 }
746 }
747 ASSERT_NE(sorted_0, std::numeric_limits<size_t>::max()) << "no sorted RAM consistency gate found";
748
749 // After the fix the first sorted gate must use zero_idx() as its index witness.
750 // RAM wire layout: w_l=index, w_r=timestamp, w_o=value, w_4=record.
751 uint32_t sorted_0_idx_w = mem.w_l()[sorted_0];
752 uint32_t sorted_0_val_w = mem.w_o()[sorted_0];
753 EXPECT_EQ(sorted_0_idx_w, circuit_builder.zero_idx());
754
755 // Build the malicious copy: move the write record to index p-1.
756 // Sorted chain would become: [(p-1,ts,999), (0,0,1), (1,1,2), final(2,2,3)].
757 // Mutating sorted_0's index_witness (== zero_idx()) to p-1 violates the fix_witness
758 // arithmetic gate and must be rejected.
759 Builder bad_builder = circuit_builder;
760 auto& vars = const_cast<std::vector<FF>&>(bad_builder.get_variables());
761 const FF p_minus_1 = -FF(1);
762 vars[bad_builder.real_variable_index[sorted_0_idx_w]] = p_minus_1;
763 vars[bad_builder.real_variable_index[sorted_0_val_w]] = FF(999);
764 vars[bad_builder.real_variable_index[idx_witness]] = p_minus_1;
765 vars[bad_builder.real_variable_index[write_val_w]] = FF(999);
766
767 // Run CircuitChecker: expected error in the arithmetic sub-relation from zero_idx's fix_witness.
768 EXPECT_TRUE(CircuitChecker::check(circuit_builder));
769 EXPECT_FALSE(CircuitChecker::check(bad_builder));
770
771 // Run full protocol.
772 TestFixture::prove_and_verify(circuit_builder, /*expected_result=*/true);
773 TestFixture::prove_and_verify(bad_builder, /*expected_result=*/false);
774}
775
776// Test malicious witness "out-of-bounds" RAM access
777TYPED_TEST(UltraHonkTests, RamOutOfBoundsRead)
778{
779 using Flavor = TypeParam;
780 using FF = typename Flavor::FF;
782
783 // Create a RAM array of size 5
784 const size_t ram_size = 5;
785 size_t ram_id = injector.builder.create_RAM_array(ram_size);
786
787 // Initialize all elements
788 for (size_t i = 0; i < ram_size; ++i) {
789 auto init_val = injector.builder.add_variable(FF(100 + i));
790 injector.builder.init_RAM_element(ram_id, i, init_val);
791 }
792
793 // Create a malicious/invalid index witness:
794 FF good_index = FF(2);
795 FF bad_index = FF(99);
796 auto malicious_index = injector.add_malicious_variable(good_index, bad_index);
797
798 // Create a read using the malicious index
799 auto read_result = injector.builder.read_RAM_array(ram_id, malicious_index);
800
801 // Use the read result in a constraint to ensure it's checked
802 auto expected = injector.builder.add_variable(FF(102)); // value at index 2
803 injector.builder.assert_equal(read_result, expected);
804
805 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(injector.builder);
806
807 // Run CircuitChecker
808 // Expected error: "Failed tag check."
809 EXPECT_TRUE(CircuitChecker::check(injector.builder));
810 auto bad_builder = injector.create_builder_with_malicious_witnesses();
811 EXPECT_FALSE(CircuitChecker::check(bad_builder));
812
813 // Run full protocol
814 auto [good_instance, bad_instance] = injector.create_instances();
815 TestFixture::prove_and_verify(good_instance, /*expected_result=*/true);
816 TestFixture::prove_and_verify(bad_instance, /*expected_result=*/false);
817}
818
819// Test malicious witness "out-of-bounds" RAM write
820TYPED_TEST(UltraHonkTests, RamOutOfBoundsWrite)
821{
822 using Flavor = TypeParam;
823 using FF = typename Flavor::FF;
825
826 // Create a RAM array of size 5
827 const size_t ram_size = 5;
828 size_t ram_id = injector.builder.create_RAM_array(ram_size);
829
830 // Initialize all elements
831 for (size_t i = 0; i < ram_size; ++i) {
832 auto init_val = injector.builder.add_variable(FF(100 + i));
833 injector.builder.init_RAM_element(ram_id, i, init_val);
834 }
835
836 // Create a malicious/invalid index witness:
837 FF good_index = FF(2);
838 FF bad_index = FF(99);
839 auto malicious_index = injector.add_malicious_variable(good_index, bad_index);
840
841 // Create a write using the malicious index
842 auto write_value = injector.builder.add_variable(FF(42));
843 injector.builder.write_RAM_array(ram_id, malicious_index, write_value);
844
845 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(injector.builder);
846
847 // Run CircuitChecker
848 // Expected error: "Failed tag check."
849 EXPECT_TRUE(CircuitChecker::check(injector.builder));
850 auto bad_builder = injector.create_builder_with_malicious_witnesses();
851 EXPECT_FALSE(CircuitChecker::check(bad_builder));
852
853 // Run full protocol
854 auto [good_instance, bad_instance] = injector.create_instances();
855 TestFixture::prove_and_verify(good_instance, /*expected_result=*/true);
856 TestFixture::prove_and_verify(bad_instance, /*expected_result=*/false);
857}
858
859// Test malicious witness "out-of-bounds" ROM access
860TYPED_TEST(UltraHonkTests, RomOutOfBoundsRead)
861{
862 using Flavor = TypeParam;
863 using FF = typename Flavor::FF;
865
866 // Create a ROM array of size 5
867 const size_t rom_size = 5;
868 size_t rom_id = injector.builder.create_ROM_array(rom_size);
869
870 // Initialize all elements
871 for (size_t i = 0; i < rom_size; ++i) {
872 auto init_val = injector.builder.add_variable(FF(100 + i));
873 injector.builder.set_ROM_element(rom_id, i, init_val);
874 }
875
876 // Create a malicious/invalid index witness:
877 FF good_index = FF(2);
878 FF bad_index = FF(99);
879 auto malicious_index = injector.add_malicious_variable(good_index, bad_index);
880
881 // Create a read using the malicious index
882 auto read_result = injector.builder.read_ROM_array(rom_id, malicious_index);
883
884 // Use the read result in a constraint to ensure it's checked
885 auto expected = injector.builder.add_variable(FF(102)); // value at index 2
886 injector.builder.assert_equal(read_result, expected);
887
888 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(injector.builder);
889
890 // Run CircuitChecker
891 // Expected error: "Failed tag check."
892 EXPECT_TRUE(CircuitChecker::check(injector.builder));
893 auto bad_builder = injector.create_builder_with_malicious_witnesses();
894 EXPECT_FALSE(CircuitChecker::check(bad_builder));
895
896 // Run full protocol
897 auto [good_instance, bad_instance] = injector.create_instances();
898 TestFixture::prove_and_verify(good_instance, /*expected_result=*/true);
899 TestFixture::prove_and_verify(bad_instance, /*expected_result=*/false);
900}
901
902// Test malicious witness "out-of-bounds" TwinRom access
903TYPED_TEST(UltraHonkTests, TwinRomOutOfBoundsRead)
904{
905 using Flavor = TypeParam;
906 using FF = typename Flavor::FF;
909 using field_pair_ct = std::array<field_ct, 2>;
910 using twin_rom_table_ct = stdlib::twin_rom_table<Builder>;
913
914 // Create a TwinROM array of size 5
915 std::vector<field_pair_ct> table_values;
916 table_values.emplace_back(
917 field_pair_ct{ witness_ct(&injector.builder, bb::fr(1)), witness_ct(&injector.builder, bb::fr(2)) });
918 table_values.emplace_back(
919 field_pair_ct{ witness_ct(&injector.builder, bb::fr(3)), witness_ct(&injector.builder, bb::fr(4)) });
920 twin_rom_table_ct table(table_values);
921
922 // Create a malicious/invalid index witness:
923 FF good_index = FF(1);
924 FF bad_index = FF(99);
925 auto malicious_index = injector.add_malicious_variable(good_index, bad_index);
926
927 // Create a read using the malicious index
928 auto read_result = table[field_ct::from_witness_index(&injector.builder, malicious_index)];
929
930 // Use the read result in a constraint to ensure it's checked
931 auto expected = injector.builder.add_variable(FF(3)); // value at index 1
932 injector.builder.assert_equal(read_result[0].get_witness_index(), expected);
933
934 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(injector.builder);
935
936 // Run CircuitChecker
937 // Expected error: "Failed tag check."
938 EXPECT_TRUE(CircuitChecker::check(injector.builder));
939 auto bad_builder = injector.create_builder_with_malicious_witnesses();
940 EXPECT_FALSE(CircuitChecker::check(bad_builder));
941
942 // Run full protocol
943 auto [good_instance, bad_instance] = injector.create_instances();
944 TestFixture::prove_and_verify(good_instance, /*expected_result=*/true);
945 TestFixture::prove_and_verify(bad_instance, /*expected_result=*/false);
946}
#define BB_ASSERT_GTE(left, right,...)
Definition assert.hpp:128
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:83
#define BB_ASSERT_LTE(left, right,...)
Definition assert.hpp:158
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessageRegex)
Definition assert.hpp:224
#define BB_DISABLE_ASSERTS()
Definition assert.hpp:33
static void build_ROM_table_length_zero(auto &circuit_builder)
static void build_RAM_table_length_zero(auto &circuit_builder)
static void build_failing_ROM_table(auto &circuit_builder, size_t array_length, ROMFailureType rom_failure_type)
std::vector< fr > NativeRamTable
static void build_ROM_table_with_uninitialized_values(auto &circuit_builder, size_t array_length)
std::vector< std::array< fr, 2 > > NativeRomTable
static void build_random_ROM_table(auto &circuit_builder, size_t array_length, size_t num_pair_elts_in_ROM_table=0, const size_t read_operations=0, bool final_arithmetic_gate_and_read=true)
build a random ROM table, together with some read ops and an arithmetic gate. includes several compat...
static void build_random_RAM_table(auto &circuit_builder, size_t array_length, const size_t read_write_operations=0, bool final_arithmetic_gate_and_read=true)
typename Curve::ScalarField FF
ECCVMCircuitBuilder CircuitBuilder
Test utility for injecting malicious witness values to test failure modes.
Builder create_builder_with_malicious_witnesses()
Create a copy of the builder with malicious values injected.
std::pair< std::shared_ptr< ProverInstance >, std::shared_ptr< ProverInstance > > create_instances()
Create two prover instances, one based on the good witness values and one based on the malicious valu...
uint32_t add_malicious_variable(const FF &good_val, const FF &bad_val)
Add a "good" variable to the builder and specify a malicious value to inject later.
static bool check(const Builder &circuit)
Check the witness satisifies the circuit.
Child class of UltraFlavor that runs with ZK Sumcheck.
virtual uint32_t get_random_uint32()=0
static field_t from_witness_index(Builder *ctx, uint32_t witness_index)
Definition field.cpp:67
MemoryStore mem
numeric::RNG & engine
testing::Types< UltraFlavor, UltraKeccakFlavor, MegaFlavor > FlavorTypes
stdlib::witness_t< Builder > witness_ct
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
TYPED_TEST_SUITE(CommitmentKeyTest, Curves)
TYPED_TEST(CommitmentKeyTest, CommitToZeroPoly)
UltraCircuitBuilder_< UltraExecutionTraceBlocks > UltraCircuitBuilder
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
static field random_element(numeric::RNG *engine=nullptr) noexcept
static constexpr field zero()