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
23TEST(BlockConstraintMemOpEncoding, ReadFlagFalseDecodesAsRead)
24{
26 .block_id = Acir::BlockId{ .value = 0 },
27 .init = { Acir::Witness{ .value = 0 } },
28 .block_type = Acir::BlockType{ .value = Acir::BlockType::CallData{ .value = 0 } },
29 };
31
33 .block_id = Acir::BlockId{ .value = 0 },
34 .op = Acir::MemOp{ .read = false, .index = Acir::Witness{ .value = 1 }, .value = Acir::Witness{ .value = 2 } },
35 };
36
37 EXPECT_NO_THROW(add_memory_op_to_block_constraint(mem_op, block));
38
39 ASSERT_EQ(block.trace.size(), 1);
40 EXPECT_EQ(block.trace[0].access_type, AccessType::Read);
41 EXPECT_EQ(block.trace[0].index, 1);
42 EXPECT_EQ(block.trace[0].value, 2);
43}
44
45TEST(BlockConstraintMemOpEncoding, AccessTypeEncodesToReadFlag)
46{
47 const MemOp read_op{
48 .access_type = AccessType::Read,
49 .index = 1,
50 .value = 2,
51 };
52 const MemOp write_op{
53 .access_type = AccessType::Write,
54 .index = 3,
55 .value = 4,
56 };
57
58 EXPECT_FALSE(mem_op_to_acir_mem_op(read_op).read);
59 EXPECT_TRUE(mem_op_to_acir_mem_op(write_op).read);
60}
61
62template <typename Builder_, size_t TableSize_, size_t NumReads_> struct ROMTestParams {
63 using Builder = Builder_;
64 static constexpr size_t table_size = TableSize_;
65 static constexpr size_t num_reads = NumReads_;
66};
67
68template <typename Builder_, size_t table_size, size_t num_reads> class ROMTestingFunctions {
69 public:
71 using Builder = Builder_;
73 public:
74 enum class Target : uint8_t { None, ReadValueIncremented };
76 {
78 if constexpr (num_reads > 0 && table_size > 0) {
79 targets.push_back(Target::ReadValueIncremented);
80 }
81 return targets;
82 };
83 static std::vector<std::string> get_labels()
84 {
85 std::vector<std::string> labels = { "None" };
86 if constexpr (num_reads > 0 && table_size > 0) {
87 labels.push_back("ReadValueIncremented");
88 }
89 return labels;
90 };
91 };
92
94
95 static void generate_constraints(AcirConstraint& memory_constraint, WitnessVector& witness_values)
96 {
97 // Create initial memory values "natively"
98 std::vector<bb::fr> table_values;
99 table_values.reserve(table_size);
100 for (size_t _i = 0; _i < table_size; _i++) {
101 table_values.push_back(bb::fr::random_element());
102 }
103
104 // `init_poly` represents the _initial values_ of the circuit.
105 std::vector<uint32_t> init_indices;
106 for (const auto& val : table_values) {
107 uint32_t value_index = add_to_witness_and_track_indices(witness_values, val);
108 // push the circuit incarnation of the value in `init_indices`
109 init_indices.push_back(value_index);
110 }
111
112 // Initialize and create memory operations
114
115 // Add index witness only if we have a non-empty table
116 if constexpr (table_size > 0) {
117 for (size_t _i = 0; _i < num_reads; ++_i) {
118 const size_t rom_index_to_read = static_cast<size_t>(engine.get_random_uint32() % table_size);
119 // Add value witness
120 bb::fr read_value = table_values[rom_index_to_read];
121
122 const uint32_t index_for_read =
123 add_to_witness_and_track_indices(witness_values, bb::fr(rom_index_to_read));
124 const uint32_t value_for_read = add_to_witness_and_track_indices(witness_values, read_value);
125
126 const MemOp read_op = { .access_type = AccessType::Read,
127 .index = index_for_read,
128 .value = value_for_read };
129
130 trace.push_back(read_op);
131 }
132 }
133 // Create the MemoryConstraint
134 memory_constraint = AcirConstraint{ .init = init_indices, .trace = trace, .type = BlockType::ROM };
135 }
136
138 [[maybe_unused]] AcirConstraint memory_constraint,
139 WitnessVector witness_values,
140 const InvalidWitness::Target& invalid_witness_target)
141 {
142 switch (invalid_witness_target) {
144 break;
146 if constexpr (num_reads > 0 && table_size > 0) {
147 // Tamper with a random read value
148 const size_t random_read = static_cast<size_t>(engine.get_random_uint32() % num_reads);
149 // Each read has 2 witness values: index at offset 0, value at offset 1
150 // The reads start after the table_size init values
151 const size_t read_value_witness_index = table_size + (random_read * 2) + 1;
152 witness_values[read_value_witness_index] += bb::fr(1);
153 }
154 break;
155 }
156
157 return { memory_constraint, witness_values };
158 }
159};
160template <typename Params>
161class ROMTest : public ::testing::Test,
162 public TestClass<ROMTestingFunctions<typename Params::Builder, Params::table_size, Params::num_reads>> {
163 protected:
165};
166
167using ROMTestConfigs = testing::Types<ROMTestParams<UltraCircuitBuilder, 0, 0>,
174
175TYPED_TEST(ROMTest, GenerateVKFromConstraints)
176{
177 using Flavor =
179 TestFixture::template test_vk_independence<Flavor>();
180}
181
183{
184 TestFixture::test_tampering();
185}
186
187template <typename Builder_, size_t TableSize_, size_t NumReads_, size_t NumWrites_> struct RAMTestParams {
188 using Builder = Builder_;
189 static constexpr size_t table_size = TableSize_;
190 static constexpr size_t num_reads = NumReads_;
191 static constexpr size_t num_writes = NumWrites_;
192};
193
194template <typename Builder_, size_t table_size, size_t num_reads, size_t num_writes> class RAMTestingFunctions {
195 public:
197 using Builder = Builder_;
198
199 // Track witness value and its index in witness_values
203 };
204
206 public:
207 enum class Target : uint8_t { None, ReadValueIncremented };
209 {
210 std::vector<Target> targets = { Target::None };
211 if constexpr (num_reads > 0 && table_size > 0) {
212 targets.push_back(Target::ReadValueIncremented);
213 }
214 return targets;
215 };
216 static std::vector<std::string> get_labels()
217 {
218 std::vector<std::string> labels = { "None" };
219 if constexpr (num_reads > 0 && table_size > 0) {
220 labels.push_back("ReadValueIncremented");
221 }
222 return labels;
223 };
224 };
225
227
228 static void generate_constraints(AcirConstraint& memory_constraint, WitnessVector& witness_values)
229 {
230 // Create initial memory values "natively". RAM tables always start out initialized.
231 std::vector<bb::fr> table_values;
232 table_values.reserve(table_size);
233 for (size_t _i = 0; _i < table_size; _i++) {
234 table_values.push_back(bb::fr::random_element());
235 }
236
237 // `init_indices` contains the initial values of the circuit.
238 std::vector<uint32_t> init_indices;
239 for (size_t i = 0; i < table_size; ++i) {
240 const auto val = table_values[i];
241 uint32_t value_index = add_to_witness_and_track_indices(witness_values, val);
242 init_indices.push_back(value_index);
243 }
244 // Initialize and create memory operations
246 size_t num_reads_remaining = num_reads;
247 size_t num_writes_remaining = num_writes;
248
249 // `read_write_sequence` is a _random_ list of read and write operations.
250 std::vector<AccessType> read_write_sequence;
251 while (num_reads_remaining + num_writes_remaining > 0) {
252 bool try_read = (engine.get_random_uint32() & 1) != 0;
253 if (try_read && (num_reads_remaining > 0)) {
254 read_write_sequence.push_back(AccessType::Read);
255 num_reads_remaining--;
256 } else if (num_writes_remaining > 0) {
257 read_write_sequence.push_back(AccessType::Write);
258 num_writes_remaining--;
259 } else {
260 // writes exhausted, hence only reads left
261 for (size_t _j = 0; _j < num_reads_remaining; _j++) {
262 read_write_sequence.push_back(AccessType::Read);
263 }
264 num_reads_remaining = 0;
265 }
266 }
267
268 // Add read/writes only if we have a non-empty table
269 if constexpr (table_size > 0) {
270 for (auto& access_type : read_write_sequence) {
271 MemOp mem_op;
272 switch (access_type) {
273 case AccessType::Read: {
274 const size_t ram_index_to_read = static_cast<size_t>(engine.get_random_uint32() % table_size);
275 const uint32_t index_for_read =
276 add_to_witness_and_track_indices(witness_values, bb::fr(ram_index_to_read));
277 bb::fr read_value = table_values[ram_index_to_read];
278 const uint32_t value_for_read = add_to_witness_and_track_indices(witness_values, read_value);
279
280 mem_op = { .access_type = AccessType::Read, .index = index_for_read, .value = value_for_read };
281 trace.push_back(mem_op);
282 break;
283 }
284 case AccessType::Write: {
285 const size_t ram_index_to_write = static_cast<size_t>(engine.get_random_uint32() % table_size);
286 const uint32_t index_to_write =
287 add_to_witness_and_track_indices(witness_values, bb::fr(ram_index_to_write));
288 bb::fr write_value = bb::fr::random_element();
289 const uint32_t value_to_write = add_to_witness_and_track_indices(witness_values, write_value);
290
291 // Update the table_values to reflect this write
292 table_values[ram_index_to_write] = write_value;
293
294 mem_op = { .access_type = AccessType::Write, .index = index_to_write, .value = value_to_write };
295 trace.push_back(mem_op);
296 break;
297 }
298 }
299 }
300 }
301
302 // Create the MemoryConstraint
303 memory_constraint = AcirConstraint{ .init = init_indices, .trace = trace, .type = BlockType::RAM };
304 }
305
307 [[maybe_unused]] AcirConstraint memory_constraint,
308 WitnessVector witness_values,
309 const InvalidWitness::Target& invalid_witness_target)
310 {
311 switch (invalid_witness_target) {
313 break;
315 if constexpr (num_reads > 0 && table_size > 0) {
316 // Tamper with a random read value
317 size_t random_read_idx = static_cast<size_t>(engine.get_random_uint32() % (num_reads + num_writes));
318 while (memory_constraint.trace[random_read_idx].access_type != AccessType::Read) {
319 // Find a read operation
320 random_read_idx = static_cast<size_t>(engine.get_random_uint32() % (num_reads + num_writes));
321 }
322 const uint32_t witness_idx = memory_constraint.trace[random_read_idx].value;
323 witness_values[witness_idx] += bb::fr(1);
324 }
325 break;
326 }
327
328 return { memory_constraint, witness_values };
329 }
330};
331
332template <typename Params>
334 : public ::testing::Test,
335 public TestClass<
336 RAMTestingFunctions<typename Params::Builder, Params::table_size, Params::num_reads, Params::num_writes>> {
337 protected:
339};
340
341// Failure tests are impossible in the scenario with only writes.
342using RAMTestConfigs = testing::Types<RAMTestParams<UltraCircuitBuilder, 0, 0, 0>,
350
352
353TYPED_TEST(RAMTest, GenerateVKFromConstraints)
354{
355 using Flavor =
357 TestFixture::template test_vk_independence<Flavor>();
358}
359
361{
362 TestFixture::test_tampering();
363}
364
365template <CallDataType CallDataType_, size_t CallDataSize_, size_t NumReads_> struct CallDataTestParams {
366 static constexpr CallDataType calldata_type = CallDataType_;
367 static constexpr size_t calldata_size = CallDataSize_;
368 static constexpr size_t num_reads = NumReads_;
369};
370
371template <CallDataType calldata_type, size_t calldata_size, size_t num_reads> class CallDataTestingFunctions {
372 public:
375
377 public:
378 enum class Target : uint8_t { None, ReadValueIncremented };
379
381 {
382 if constexpr (num_reads > 0) {
384 }
385 return { Target::None };
386 }
387
388 static std::vector<std::string> get_labels()
389 {
390 if constexpr (num_reads > 0) {
391 return { "None", "ReadValueIncremented" };
392 }
393 return { "None" };
394 }
395 };
396
398
399 static void generate_constraints(AcirConstraint& memory_constraint, WitnessVector& witness_values)
400 {
401
402 // Create initial memory values "natively". Memory tables always start out initialized.
403 std::vector<bb::fr> calldata_values;
404 calldata_values.reserve(calldata_size);
405 for (size_t _i = 0; _i < calldata_size; _i++) {
406 calldata_values.push_back(bb::fr::random_element());
407 }
408
409 // `init_indices` contains the initial values of the circuit.
410 std::vector<uint32_t> init_indices;
411 for (size_t i = 0; i < calldata_size; ++i) {
412 uint32_t value_index = add_to_witness_and_track_indices(witness_values, calldata_values[i]);
413 init_indices.push_back(value_index);
414 }
415 // Initialize and create memory operations
417
418 // Add read operations
419 if constexpr (calldata_size > 0) {
420 for (size_t idx = 0; idx < num_reads; ++idx) {
421 MemOp mem_op;
422 const size_t calldata_idx_to_read = static_cast<size_t>(engine.get_random_uint32() % calldata_size);
423 const uint32_t index_for_read =
424 add_to_witness_and_track_indices(witness_values, bb::fr(calldata_idx_to_read));
425 bb::fr read_value = calldata_values[calldata_idx_to_read];
426 const uint32_t value_for_read = add_to_witness_and_track_indices(witness_values, read_value);
427
428 mem_op = { .access_type = AccessType::Read, .index = index_for_read, .value = value_for_read };
429 trace.push_back(mem_op);
430 }
431 }
432
433 // Create the MemoryConstraint
434 memory_constraint = AcirConstraint{
435 .init = init_indices, .trace = trace, .type = BlockType::CallData, .calldata_id = calldata_type
436 };
437 }
438
440 AcirConstraint memory_constraint,
441 WitnessVector witness_values,
442 const InvalidWitness::Target& invalid_witness_target)
443 {
444 switch (invalid_witness_target) {
446 break;
448 // Tamper with a random read value using the recorded witness index
449 if constexpr (num_reads > 0) {
450 const size_t random_read_idx = static_cast<size_t>(engine.get_random_uint32() % num_reads);
451 const uint32_t witness_idx = memory_constraint.trace[random_read_idx].index;
452 witness_values[witness_idx] += bb::fr(1);
453 }
454 break;
455 }
456
457 return { memory_constraint, witness_values };
458 }
459};
460
461using CallDataTestConfigs = testing::Types<CallDataTestParams<CallDataType::KernelCalldata, 0, 0>,
467
468template <typename Params>
470 : public ::testing::Test,
471 public TestClass<CallDataTestingFunctions<Params::calldata_type, Params::calldata_size, Params::num_reads>> {
472 protected:
474};
475
477
478TYPED_TEST(CallDataTests, GenerateVKFromConstraints)
479{
480 TestFixture::template test_vk_independence<MegaFlavor>();
481}
482
484{
485 TestFixture::test_tampering();
486}
487
488template <size_t returndata_size>
489
491 public:
494
495 // There is no tampering that can be done for ReturnData as the only thing that a return data opcode does is
496 // adding data to the return data bus vector and constraining such data to be equal to the data with which the
497 // memory operation was initialized
499 public:
500 enum class Target : uint8_t { None };
501
502 static std::vector<Target> get_all() { return { Target::None }; };
503
504 static std::vector<std::string> get_labels() { return { "None" }; };
505 };
506
508
509 static void generate_constraints(AcirConstraint& memory_constraint, WitnessVector& witness_values)
510 {
511 // Create initial memory values "natively". Memory tables always start out initialized.
512 std::vector<bb::fr> returndata_values;
513 returndata_values.reserve(returndata_size);
514 for (size_t _i = 0; _i < returndata_size; _i++) {
515 returndata_values.push_back(bb::fr::random_element());
516 }
517
518 // `init_indices` contains the initial values of the circuit.
519 std::vector<uint32_t> init_indices;
520 for (size_t i = 0; i < returndata_size; ++i) {
521 uint32_t value_index = add_to_witness_and_track_indices(witness_values, returndata_values[i]);
522 init_indices.push_back(value_index);
523 }
524
525 // Create the MemoryConstraint
526 memory_constraint = AcirConstraint{ .init = init_indices, .trace = {}, .type = BlockType::ReturnData };
527 }
528
530 [[maybe_unused]] AcirConstraint memory_constraint,
531 [[maybe_unused]] WitnessVector witness_values,
532 const InvalidWitness::Target& invalid_witness_target)
533 {
534 switch (invalid_witness_target) {
536 break;
537 }
538
539 return { memory_constraint, witness_values };
540 }
541};
542
543template <size_t ReturnDataSize_> class ReturnDataTestsParams {
544 public:
545 static constexpr size_t returndata_size = ReturnDataSize_;
546};
547
548template <typename Params>
549class ReturnDataTests : public ::testing::Test, public TestClass<ReturnDataTestingFunctions<Params::returndata_size>> {
550 protected:
552};
553
554using ReturnDataTestConfigs = testing::Types<ReturnDataTestsParams<0>, ReturnDataTestsParams<10>>;
555
557
558TYPED_TEST(ReturnDataTests, GenerateVKFromConstraints)
559{
560 TestFixture::template test_vk_independence<MegaFlavor>();
561}
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< MegaCircuitBuilder, 0, 0, 0 >, RAMTestParams< MegaCircuitBuilder, 10, 0, 0 >, RAMTestParams< MegaCircuitBuilder, 10, 0, 10 >, RAMTestParams< MegaCircuitBuilder, 10, 10, 0 > > 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
void add_memory_op_to_block_constraint(Acir::Opcode::MemoryOp const &mem_op, BlockConstraint &block)
Process memory operation, either read or write, and update the BlockConstraint type accordingly.
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
BlockConstraint memory_init_to_block_constraint(Acir::Opcode::MemoryInit const &mem_init)
========= MEMORY OPERATIONS ========== ///
Acir::MemOp mem_op_to_acir_mem_op(const MemOp &mem_op)
Convert an acir_format::MemOp to an Acir::MemOp.
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)
void read(B &it, field2< base_field, Params > &value)
TYPED_TEST_SUITE(CommitmentKeyTest, Curves)
field< Bn254FrParams > fr
Definition fr.hpp:155
TYPED_TEST(CommitmentKeyTest, CommitToZeroPoly)
TEST(BoomerangMegaCircuitBuilder, BasicCircuit)
MegaCircuitBuilder_< field< Bn254FrParams > > MegaCircuitBuilder
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
uint32_t value
Definition acir.hpp:5682
std::variant< Memory, CallData, ReturnData > value
Definition acir.hpp:5733
bool read
Definition acir.hpp:6273
Acir::BlockId block_id
Definition acir.hpp:6414
Acir::BlockId block_id
Definition acir.hpp:6366
uint32_t value
Definition acir.hpp:4233
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