Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
block_constraint.test.cpp
Go to the documentation of this file.
2#include "acir_format.hpp"
8
12
13#include <cstdint>
14#include <gtest/gtest.h>
15#include <vector>
16
17using namespace acir_format;
18
19namespace {
21} // namespace
22
23template <typename Builder_, size_t TableSize_, size_t NumReads_> struct ROMTestParams {
24 using Builder = Builder_;
25 static constexpr size_t table_size = TableSize_;
26 static constexpr size_t num_reads = NumReads_;
27};
28
29template <typename Builder_, size_t table_size, size_t num_reads> class ROMTestingFunctions {
30 public:
32 using Builder = Builder_;
34 public:
35 enum class Target : uint8_t { None, ReadValueIncremented };
37 {
39 if constexpr (num_reads > 0 && table_size > 0) {
40 targets.push_back(Target::ReadValueIncremented);
41 }
42 return targets;
43 };
44 static std::vector<std::string> get_labels()
45 {
46 std::vector<std::string> labels = { "None" };
47 if constexpr (num_reads > 0 && table_size > 0) {
48 labels.push_back("ReadValueIncremented");
49 }
50 return labels;
51 };
52 };
53
55
56 static void generate_constraints(AcirConstraint& memory_constraint, WitnessVector& witness_values)
57 {
58 // Create initial memory values "natively"
59 std::vector<bb::fr> table_values;
60 table_values.reserve(table_size);
61 for (size_t _i = 0; _i < table_size; _i++) {
62 table_values.push_back(bb::fr::random_element());
63 }
64
65 // `init_poly` represents the _initial values_ of the circuit.
66 std::vector<uint32_t> init_indices;
67 for (const auto& val : table_values) {
68 uint32_t value_index = add_to_witness_and_track_indices(witness_values, val);
69 // push the circuit incarnation of the value in `init_indices`
70 init_indices.push_back(value_index);
71 }
72
73 // Initialize and create memory operations
75
76 // Add index witness only if we have a non-empty table
77 if constexpr (table_size > 0) {
78 for (size_t _i = 0; _i < num_reads; ++_i) {
79 const size_t rom_index_to_read = static_cast<size_t>(engine.get_random_uint32() % table_size);
80 // Add value witness
81 bb::fr read_value = table_values[rom_index_to_read];
82
83 const uint32_t index_for_read =
84 add_to_witness_and_track_indices(witness_values, bb::fr(rom_index_to_read));
85 const uint32_t value_for_read = add_to_witness_and_track_indices(witness_values, read_value);
86
87 const MemOp read_op = { .access_type = AccessType::Read,
88 .index = index_for_read,
89 .value = value_for_read };
90
91 trace.push_back(read_op);
92 }
93 }
94 // Create the MemoryConstraint
95 memory_constraint = AcirConstraint{ .init = init_indices, .trace = trace, .type = BlockType::ROM };
96 }
97
99 [[maybe_unused]] AcirConstraint memory_constraint,
100 WitnessVector witness_values,
101 const InvalidWitness::Target& invalid_witness_target)
102 {
103 switch (invalid_witness_target) {
105 break;
107 if constexpr (num_reads > 0 && table_size > 0) {
108 // Tamper with a random read value
109 const size_t random_read = static_cast<size_t>(engine.get_random_uint32() % num_reads);
110 // Each read has 2 witness values: index at offset 0, value at offset 1
111 // The reads start after the table_size init values
112 const size_t read_value_witness_index = table_size + (random_read * 2) + 1;
113 witness_values[read_value_witness_index] += bb::fr(1);
114 }
115 break;
116 }
117
118 return { memory_constraint, witness_values };
119 }
120};
121template <typename Params>
122class ROMTest : public ::testing::Test,
123 public TestClass<ROMTestingFunctions<typename Params::Builder, Params::table_size, Params::num_reads>> {
124 protected:
126};
127
128using ROMTestConfigs = testing::Types<ROMTestParams<UltraCircuitBuilder, 0, 0>,
135
136TYPED_TEST(ROMTest, GenerateVKFromConstraints)
137{
138 using Flavor =
140 TestFixture::template test_vk_independence<Flavor>();
141}
142
144{
145 TestFixture::test_tampering();
146}
147
148template <typename Builder_, size_t TableSize_, size_t NumReads_, size_t NumWrites_> struct RAMTestParams {
149 using Builder = Builder_;
150 static constexpr size_t table_size = TableSize_;
151 static constexpr size_t num_reads = NumReads_;
152 static constexpr size_t num_writes = NumWrites_;
153};
154
155template <typename Builder_, size_t table_size, size_t num_reads, size_t num_writes> class RAMTestingFunctions {
156 public:
158 using Builder = Builder_;
159
160 // Track witness value and its index in witness_values
164 };
165
167 public:
168 enum class Target : uint8_t { None, ReadValueIncremented };
170 {
171 std::vector<Target> targets = { Target::None };
172 if constexpr (num_reads > 0 && table_size > 0) {
173 targets.push_back(Target::ReadValueIncremented);
174 }
175 return targets;
176 };
177 static std::vector<std::string> get_labels()
178 {
179 std::vector<std::string> labels = { "None" };
180 if constexpr (num_reads > 0 && table_size > 0) {
181 labels.push_back("ReadValueIncremented");
182 }
183 return labels;
184 };
185 };
186
188
189 static void generate_constraints(AcirConstraint& memory_constraint, WitnessVector& witness_values)
190 {
191 // Create initial memory values "natively". RAM tables always start out initialized.
192 std::vector<bb::fr> table_values;
193 table_values.reserve(table_size);
194 for (size_t _i = 0; _i < table_size; _i++) {
195 table_values.push_back(bb::fr::random_element());
196 }
197
198 // `init_indices` contains the initial values of the circuit.
199 std::vector<uint32_t> init_indices;
200 for (size_t i = 0; i < table_size; ++i) {
201 const auto val = table_values[i];
202 uint32_t value_index = add_to_witness_and_track_indices(witness_values, val);
203 init_indices.push_back(value_index);
204 }
205 // Initialize and create memory operations
207 size_t num_reads_remaining = num_reads;
208 size_t num_writes_remaining = num_writes;
209
210 // `read_write_sequence` is a _random_ list of read and write operations.
211 std::vector<AccessType> read_write_sequence;
212 while (num_reads_remaining + num_writes_remaining > 0) {
213 bool try_read = (engine.get_random_uint32() & 1) != 0;
214 if (try_read && (num_reads_remaining > 0)) {
215 read_write_sequence.push_back(AccessType::Read);
216 num_reads_remaining--;
217 } else if (num_writes_remaining > 0) {
218 read_write_sequence.push_back(AccessType::Write);
219 num_writes_remaining--;
220 } else {
221 // writes exhausted, hence only reads left
222 for (size_t _j = 0; _j < num_reads_remaining; _j++) {
223 read_write_sequence.push_back(AccessType::Read);
224 }
225 num_reads_remaining = 0;
226 }
227 }
228
229 // Add read/writes only if we have a non-empty table
230 if constexpr (table_size > 0) {
231 for (auto& access_type : read_write_sequence) {
232 MemOp mem_op;
233 switch (access_type) {
234 case AccessType::Read: {
235 const size_t ram_index_to_read = static_cast<size_t>(engine.get_random_uint32() % table_size);
236 const uint32_t index_for_read =
237 add_to_witness_and_track_indices(witness_values, bb::fr(ram_index_to_read));
238 bb::fr read_value = table_values[ram_index_to_read];
239 const uint32_t value_for_read = add_to_witness_and_track_indices(witness_values, read_value);
240
241 mem_op = { .access_type = AccessType::Read, .index = index_for_read, .value = value_for_read };
242 trace.push_back(mem_op);
243 break;
244 }
245 case AccessType::Write: {
246 const size_t ram_index_to_write = static_cast<size_t>(engine.get_random_uint32() % table_size);
247 const uint32_t index_to_write =
248 add_to_witness_and_track_indices(witness_values, bb::fr(ram_index_to_write));
249 bb::fr write_value = bb::fr::random_element();
250 const uint32_t value_to_write = add_to_witness_and_track_indices(witness_values, write_value);
251
252 // Update the table_values to reflect this write
253 table_values[ram_index_to_write] = write_value;
254
255 mem_op = { .access_type = AccessType::Write, .index = index_to_write, .value = value_to_write };
256 trace.push_back(mem_op);
257 break;
258 }
259 }
260 }
261 }
262
263 // Create the MemoryConstraint
264 memory_constraint = AcirConstraint{ .init = init_indices, .trace = trace, .type = BlockType::RAM };
265 }
266
268 [[maybe_unused]] AcirConstraint memory_constraint,
269 WitnessVector witness_values,
270 const InvalidWitness::Target& invalid_witness_target)
271 {
272 switch (invalid_witness_target) {
274 break;
276 if constexpr (num_reads > 0 && table_size > 0) {
277 // Tamper with a random read value
278 size_t random_read_idx = static_cast<size_t>(engine.get_random_uint32() % (num_reads + num_writes));
279 while (memory_constraint.trace[random_read_idx].access_type != AccessType::Read) {
280 // Find a read operation
281 random_read_idx = static_cast<size_t>(engine.get_random_uint32() % (num_reads + num_writes));
282 }
283 const uint32_t witness_idx = memory_constraint.trace[random_read_idx].value;
284 witness_values[witness_idx] += bb::fr(1);
285 }
286 break;
287 }
288
289 return { memory_constraint, witness_values };
290 }
291};
292
293template <typename Params>
295 : public ::testing::Test,
296 public TestClass<
297 RAMTestingFunctions<typename Params::Builder, Params::table_size, Params::num_reads, Params::num_writes>> {
298 protected:
300};
301
302// Failure tests are impossible in the scenario with only writes.
303using RAMTestConfigs = testing::Types<RAMTestParams<UltraCircuitBuilder, 0, 0, 0>,
313
315
316TYPED_TEST(RAMTest, GenerateVKFromConstraints)
317{
318 using Flavor =
320 TestFixture::template test_vk_independence<Flavor>();
321}
322
324{
325 TestFixture::test_tampering();
326}
327
328template <CallDataType CallDataType_, size_t CallDataSize_, size_t NumReads_> struct CallDataTestParams {
329 static constexpr CallDataType calldata_type = CallDataType_;
330 static constexpr size_t calldata_size = CallDataSize_;
331 static constexpr size_t num_reads = NumReads_;
332};
333
334template <CallDataType calldata_type, size_t calldata_size, size_t num_reads> class CallDataTestingFunctions {
335 public:
338
340 public:
341 enum class Target : uint8_t { None, ReadValueIncremented };
342
344 {
345 if constexpr (num_reads > 0) {
347 }
348 return { Target::None };
349 }
350
351 static std::vector<std::string> get_labels()
352 {
353 if constexpr (num_reads > 0) {
354 return { "None", "ReadValueIncremented" };
355 }
356 return { "None" };
357 }
358 };
359
361
362 static void generate_constraints(AcirConstraint& memory_constraint, WitnessVector& witness_values)
363 {
364
365 // Create initial memory values "natively". Memory tables always start out initialized.
366 std::vector<bb::fr> calldata_values;
367 calldata_values.reserve(calldata_size);
368 for (size_t _i = 0; _i < calldata_size; _i++) {
369 calldata_values.push_back(bb::fr::random_element());
370 }
371
372 // `init_indices` contains the initial values of the circuit.
373 std::vector<uint32_t> init_indices;
374 for (size_t i = 0; i < calldata_size; ++i) {
375 uint32_t value_index = add_to_witness_and_track_indices(witness_values, calldata_values[i]);
376 init_indices.push_back(value_index);
377 }
378 // Initialize and create memory operations
380
381 // Add read operations
382 if constexpr (calldata_size > 0) {
383 for (size_t idx = 0; idx < num_reads; ++idx) {
384 MemOp mem_op;
385 const size_t calldata_idx_to_read = static_cast<size_t>(engine.get_random_uint32() % calldata_size);
386 const uint32_t index_for_read =
387 add_to_witness_and_track_indices(witness_values, bb::fr(calldata_idx_to_read));
388 bb::fr read_value = calldata_values[calldata_idx_to_read];
389 const uint32_t value_for_read = add_to_witness_and_track_indices(witness_values, read_value);
390
391 mem_op = { .access_type = AccessType::Read, .index = index_for_read, .value = value_for_read };
392 trace.push_back(mem_op);
393 }
394 }
395
396 // Create the MemoryConstraint
397 memory_constraint = AcirConstraint{
398 .init = init_indices, .trace = trace, .type = BlockType::CallData, .calldata_id = calldata_type
399 };
400 }
401
403 AcirConstraint memory_constraint,
404 WitnessVector witness_values,
405 const InvalidWitness::Target& invalid_witness_target)
406 {
407 switch (invalid_witness_target) {
409 break;
411 // Tamper with a random read value using the recorded witness index
412 if constexpr (num_reads > 0) {
413 const size_t random_read_idx = static_cast<size_t>(engine.get_random_uint32() % num_reads);
414 const uint32_t witness_idx = memory_constraint.trace[random_read_idx].index;
415 witness_values[witness_idx] += bb::fr(1);
416 }
417 break;
418 }
419
420 return { memory_constraint, witness_values };
421 }
422};
423
424using CallDataTestConfigs = testing::Types<CallDataTestParams<CallDataType::KernelCalldata, 0, 0>,
430
431template <typename Params>
433 : public ::testing::Test,
434 public TestClass<CallDataTestingFunctions<Params::calldata_type, Params::calldata_size, Params::num_reads>> {
435 protected:
437};
438
440
441TYPED_TEST(CallDataTests, GenerateVKFromConstraints)
442{
443 TestFixture::template test_vk_independence<MegaFlavor>();
444}
445
447{
448 TestFixture::test_tampering();
449}
450
451template <size_t returndata_size>
452
454 public:
457
458 // There is no tampering that can be done for ReturnData as the only thing that a return data opcode does is
459 // adding data to the return data bus vector and constraining such data to be equal to the data with which the
460 // memory operation was initialized
462 public:
463 enum class Target : uint8_t { None };
464
465 static std::vector<Target> get_all() { return { Target::None }; };
466
467 static std::vector<std::string> get_labels() { return { "None" }; };
468 };
469
471
472 static void generate_constraints(AcirConstraint& memory_constraint, WitnessVector& witness_values)
473 {
474 // Create initial memory values "natively". Memory tables always start out initialized.
475 std::vector<bb::fr> returndata_values;
476 returndata_values.reserve(returndata_size);
477 for (size_t _i = 0; _i < returndata_size; _i++) {
478 returndata_values.push_back(bb::fr::random_element());
479 }
480
481 // `init_indices` contains the initial values of the circuit.
482 std::vector<uint32_t> init_indices;
483 for (size_t i = 0; i < returndata_size; ++i) {
484 uint32_t value_index = add_to_witness_and_track_indices(witness_values, returndata_values[i]);
485 init_indices.push_back(value_index);
486 }
487
488 // Create the MemoryConstraint
489 memory_constraint = AcirConstraint{ .init = init_indices, .trace = {}, .type = BlockType::ReturnData };
490 }
491
493 [[maybe_unused]] AcirConstraint memory_constraint,
494 [[maybe_unused]] WitnessVector witness_values,
495 const InvalidWitness::Target& invalid_witness_target)
496 {
497 switch (invalid_witness_target) {
499 break;
500 }
501
502 return { memory_constraint, witness_values };
503 }
504};
505
506template <size_t ReturnDataSize_> class ReturnDataTestsParams {
507 public:
508 static constexpr size_t returndata_size = ReturnDataSize_;
509};
510
511template <typename Params>
512class ReturnDataTests : public ::testing::Test, public TestClass<ReturnDataTestingFunctions<Params::returndata_size>> {
513 protected:
515};
516
517using ReturnDataTestConfigs = testing::Types<ReturnDataTestsParams<0>, ReturnDataTestsParams<10>>;
518
520
521TYPED_TEST(ReturnDataTests, GenerateVKFromConstraints)
522{
523 TestFixture::template test_vk_independence<MegaFlavor>();
524}
testing::Types< CallDataTestParams< CallDataType::KernelCalldata, 0, 0 >, CallDataTestParams< CallDataType::KernelCalldata, 10, 5 >, CallDataTestParams< CallDataType::FirstAppCalldata, 0, 0 >, CallDataTestParams< CallDataType::FirstAppCalldata, 10, 5 >, CallDataTestParams< CallDataType::SecondAppCalldata, 10, 5 >, CallDataTestParams< CallDataType::ThirdAppCalldata, 10, 5 > > CallDataTestConfigs
testing::Types< RAMTestParams< UltraCircuitBuilder, 0, 0, 0 >, RAMTestParams< UltraCircuitBuilder, 10, 0, 0 >, RAMTestParams< UltraCircuitBuilder, 10, 0, 10 >, RAMTestParams< UltraCircuitBuilder, 10, 10, 0 >, RAMTestParams< UltraCircuitBuilder, 10, 20, 10 >, RAMTestParams< MegaCircuitBuilder, 0, 0, 0 >, RAMTestParams< MegaCircuitBuilder, 10, 0, 0 >, RAMTestParams< MegaCircuitBuilder, 10, 0, 10 >, RAMTestParams< MegaCircuitBuilder, 10, 10, 0 >, RAMTestParams< MegaCircuitBuilder, 10, 20, 10 > > RAMTestConfigs
testing::Types< ROMTestParams< UltraCircuitBuilder, 0, 0 >, ROMTestParams< UltraCircuitBuilder, 10, 0 >, ROMTestParams< UltraCircuitBuilder, 10, 20 >, ROMTestParams< MegaCircuitBuilder, 0, 0 >, ROMTestParams< MegaCircuitBuilder, 10, 0 >, ROMTestParams< MegaCircuitBuilder, 10, 20 > > ROMTestConfigs
testing::Types< ReturnDataTestsParams< 0 >, ReturnDataTestsParams< 10 > > ReturnDataTestConfigs
static std::vector< std::string > get_labels()
static ProgramMetadata generate_metadata()
static std::pair< AcirConstraint, WitnessVector > invalidate_witness(AcirConstraint memory_constraint, WitnessVector witness_values, const InvalidWitness::Target &invalid_witness_target)
static void generate_constraints(AcirConstraint &memory_constraint, WitnessVector &witness_values)
static void SetUpTestSuite()
static void SetUpTestSuite()
static std::vector< std::string > get_labels()
static std::vector< Target > get_all()
static std::pair< AcirConstraint, WitnessVector > invalidate_witness(AcirConstraint memory_constraint, WitnessVector witness_values, const InvalidWitness::Target &invalid_witness_target)
static void generate_constraints(AcirConstraint &memory_constraint, WitnessVector &witness_values)
static ProgramMetadata generate_metadata()
static void SetUpTestSuite()
static std::vector< Target > get_all()
static std::vector< std::string > get_labels()
static void generate_constraints(AcirConstraint &memory_constraint, WitnessVector &witness_values)
static ProgramMetadata generate_metadata()
static std::pair< AcirConstraint, WitnessVector > invalidate_witness(AcirConstraint memory_constraint, WitnessVector witness_values, const InvalidWitness::Target &invalid_witness_target)
static std::vector< std::string > get_labels()
static ProgramMetadata generate_metadata()
static std::pair< AcirConstraint, WitnessVector > invalidate_witness(AcirConstraint memory_constraint, WitnessVector witness_values, const InvalidWitness::Target &invalid_witness_target)
static void generate_constraints(AcirConstraint &memory_constraint, WitnessVector &witness_values)
static constexpr size_t returndata_size
virtual uint32_t get_random_uint32()=0
TestTraceContainer trace
numeric::RNG & engine
std::vector< bb::fr > WitnessVector
std::vector< uint32_t > add_to_witness_and_track_indices(std::vector< bb::fr > &witness, const T &input)
Append values to a witness vector and track their indices.
Definition utils.hpp:90
RNG & get_debug_randomness(bool reset, std::uint_fast64_t seed)
Definition engine.cpp:245
std::filesystem::path bb_crs_path()
void init_file_crs_factory(const std::filesystem::path &path)
TYPED_TEST_SUITE(CommitmentKeyTest, Curves)
field< Bn254FrParams > fr
Definition fr.hpp:155
TYPED_TEST(CommitmentKeyTest, CommitToZeroPoly)
MegaCircuitBuilder_< field< Bn254FrParams > > MegaCircuitBuilder
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
static constexpr size_t calldata_size
static constexpr CallDataType calldata_type
static constexpr size_t num_reads
static constexpr size_t table_size
static constexpr size_t num_writes
static constexpr size_t num_reads
static constexpr size_t num_reads
static constexpr size_t table_size
Struct holding the data required to add memory constraints to a circuit.
std::vector< uint32_t > init
Memory operation. index is the witness index of the memory location, and value is the witness index o...
Metadata required to create a circuit.
static field random_element(numeric::RNG *engine=nullptr) noexcept