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"
9
13
14#include <cstdint>
15#include <gtest/gtest.h>
16#include <vector>
17
18using namespace acir_format;
19
20namespace {
22} // namespace
23
27template <AccessType access_type>
28void add_constant_ops(const size_t table_size,
29 const std::vector<bb::fr>& table_values,
30 WitnessVector& witness_values,
31 std::vector<MemOp>& trace)
32{
33 const size_t table_index = static_cast<size_t>(engine.get_random_uint32() % table_size);
34 bb::fr value_fr =
35 access_type == AccessType::Read ? table_values[table_index] : table_values[table_index] + bb::fr(1);
36
37 // Index constant, value witness
38 {
42
43 const MemOp read_op = { .access_type = access_type, .index = index, .value = value };
44
45 trace.push_back(read_op);
46 }
47 // Index witness, value constant
48 {
50 add_to_witness_and_track_indices(witness_values, bb::fr(table_index)));
52
53 const MemOp read_op = { .access_type = access_type, .index = index, .value = value };
54
55 trace.push_back(read_op);
56 }
57 // Index constant, value constant
58 {
61
62 const MemOp read_op = { .access_type = access_type, .index = index, .value = value };
63
64 trace.push_back(read_op);
65 }
66}
67
68template <typename Builder_, size_t TableSize_, size_t NumReads_, bool PerformConstantOps_> struct ROMTestParams {
69 using Builder = Builder_;
70 static constexpr size_t table_size = TableSize_;
71 static constexpr size_t num_reads = NumReads_;
72 static constexpr bool perform_constant_ops = PerformConstantOps_;
73};
74
75template <typename Builder_, size_t table_size, size_t num_reads, bool perform_constant_ops> class ROMTestingFunctions {
76 public:
78 using Builder = Builder_;
80 public:
81 enum class Target : uint8_t { None, ReadValueIncremented };
83 {
85 if constexpr (num_reads > 0 && table_size > 0) {
86 targets.push_back(Target::ReadValueIncremented);
87 }
88 return targets;
89 };
90 static std::vector<std::string> get_labels()
91 {
92 std::vector<std::string> labels = { "None" };
93 if constexpr (num_reads > 0 && table_size > 0) {
94 labels.push_back("ReadValueIncremented");
95 }
96 return labels;
97 };
98 };
99
100 static void generate_constraints(AcirConstraint& memory_constraint, WitnessVector& witness_values)
101 {
102 // Create initial memory values "natively"
103 std::vector<bb::fr> table_values;
104 table_values.reserve(table_size);
105 for (size_t _i = 0; _i < table_size; _i++) {
106 table_values.push_back(bb::fr::random_element());
107 }
108
109 // `init_poly` represents the _initial values_ of the circuit.
110 std::vector<uint32_t> init_indices;
111 for (const auto& val : table_values) {
112 uint32_t value_index = add_to_witness_and_track_indices(witness_values, val);
113 // push the circuit incarnation of the value in `init_indices`
114 init_indices.push_back(value_index);
115 }
116
117 // Initialize and create memory operations
119
120 // Add index witness only if we have a non-empty table
121 if constexpr (table_size > 0) {
122 for (size_t _i = 0; _i < num_reads; ++_i) {
123 const size_t rom_index_to_read = static_cast<size_t>(engine.get_random_uint32() % table_size);
124 // Add value witness
125 bb::fr read_value = table_values[rom_index_to_read];
126
128 add_to_witness_and_track_indices(witness_values, bb::fr(rom_index_to_read)));
129 WitnessOrConstant<bb::fr> value_for_read =
131
132 const MemOp read_op = { .access_type = AccessType::Read,
133 .index = index_for_read,
134 .value = value_for_read };
135
136 trace.push_back(read_op);
137 }
138 if constexpr (perform_constant_ops) {
139 add_constant_ops<AccessType::Read>(table_size, table_values, witness_values, trace);
140 }
141 }
142 // Create the MemoryConstraint
143 memory_constraint = AcirConstraint{ .init = init_indices, .trace = trace, .type = BlockType::ROM };
144 }
145
146 static void invalidate_witness([[maybe_unused]] AcirConstraint& memory_constraint,
147 WitnessVector& witness_values,
148 const InvalidWitness::Target& invalid_witness_target)
149 {
150 switch (invalid_witness_target) {
152 break;
154 if constexpr (num_reads > 0 && table_size > 0) {
155 // Tamper with a random read value
156 const size_t random_read = static_cast<size_t>(engine.get_random_uint32() % num_reads);
157 // Each read has 2 witness values: index at offset 0, value at offset 1
158 // The reads start after the table_size init values
159 const size_t read_value_witness_index = table_size + (random_read * 2) + 1;
160 witness_values[read_value_witness_index] += bb::fr(1);
161 }
162 break;
163 }
164 }
165};
166template <typename Params>
167class ROMTest : public ::testing::Test,
168 public TestClass<ROMTestingFunctions<typename Params::Builder,
169 Params::table_size,
170 Params::num_reads,
171 Params::perform_constant_ops>> {
172 protected:
174};
175
176using ROMTestConfigs = testing::Types<ROMTestParams<UltraCircuitBuilder, 0, 0, false>,
178 ROMTestParams<UltraCircuitBuilder, 10, 0, true>, // Test the case in which there
179 // are only constant operations
182
184
185TYPED_TEST(ROMTest, GenerateVKFromConstraints)
186{
187 using Flavor =
189 TestFixture::template test_vk_independence<Flavor>();
190}
191
193{
194 TestFixture::test_tampering();
195}
196
197template <typename Builder_, size_t TableSize_, size_t NumReads_, size_t NumWrites_, bool PerformConstantOps_>
199 using Builder = Builder_;
200 static constexpr size_t table_size = TableSize_;
201 static constexpr size_t num_reads = NumReads_;
202 static constexpr size_t num_writes = NumWrites_;
203 static constexpr bool perform_constant_ops = PerformConstantOps_;
204};
205
206template <typename Builder_, size_t table_size, size_t num_reads, size_t num_writes, bool perform_constant_ops>
208 public:
210 using Builder = Builder_;
211
212 // Track witness value and its index in witness_values
216 };
217
218 // Instance member to track reads for invalidating witnesses.
220
222 public:
223 enum class Target : uint8_t { None, ReadValueIncremented };
225 {
226 std::vector<Target> targets = { Target::None };
227 if constexpr (num_reads > 0 && table_size > 0) {
228 targets.push_back(Target::ReadValueIncremented);
229 }
230 return targets;
231 };
232 static std::vector<std::string> get_labels()
233 {
234 std::vector<std::string> labels = { "None" };
235 if constexpr (num_reads > 0 && table_size > 0) {
236 labels.push_back("ReadValueIncremented");
237 }
238 return labels;
239 };
240 };
241
242 void generate_constraints(AcirConstraint& memory_constraint, WitnessVector& witness_values)
243 {
244 // Clear any previous state
245 read_values.clear();
246
247 // Create initial memory values "natively". RAM tables always start out initialized.
248 std::vector<bb::fr> table_values;
249 table_values.reserve(table_size);
250 for (size_t _i = 0; _i < table_size; _i++) {
251 table_values.push_back(bb::fr::random_element());
252 }
253
254 // `init_indices` contains the initial values of the circuit.
255 std::vector<uint32_t> init_indices;
256 for (size_t i = 0; i < table_size; ++i) {
257 const auto val = table_values[i];
258 uint32_t value_index = add_to_witness_and_track_indices(witness_values, val);
259 init_indices.push_back(value_index);
260 }
261 // Initialize and create memory operations
263 size_t num_reads_remaining = num_reads;
264 size_t num_writes_remaining = num_writes;
265
266 // `read_write_sequence` is a _random_ list of read and write operations.
267 std::vector<AccessType> read_write_sequence;
268 while (num_reads_remaining + num_writes_remaining > 0) {
269 bool try_read = (engine.get_random_uint32() & 1) != 0;
270 if (try_read && (num_reads_remaining > 0)) {
271 read_write_sequence.push_back(AccessType::Read);
272 num_reads_remaining--;
273 } else if (num_writes_remaining > 0) {
274 read_write_sequence.push_back(AccessType::Write);
275 num_writes_remaining--;
276 } else {
277 // writes exhausted, hence only reads left
278 for (size_t _j = 0; _j < num_reads_remaining; _j++) {
279 read_write_sequence.push_back(AccessType::Read);
280 }
281 num_reads_remaining = 0;
282 }
283 }
284
285 // Add read/writes only if we have a non-empty table
286 if constexpr (table_size > 0) {
287 for (auto& access_type : read_write_sequence) {
288 MemOp mem_op;
289 switch (access_type) {
290 case AccessType::Read: {
291 const size_t ram_index_to_read = static_cast<size_t>(engine.get_random_uint32() % table_size);
292 const uint32_t index_for_read =
293 add_to_witness_and_track_indices(witness_values, bb::fr(ram_index_to_read));
294 bb::fr read_value = table_values[ram_index_to_read];
295 const uint32_t value_for_read = add_to_witness_and_track_indices(witness_values, read_value);
296
297 // Record this read value and its witness index
298 read_values.push_back({ .value = read_value, .witness_index = value_for_read });
299
300 mem_op = { .access_type = AccessType::Read,
301 .index = WitnessOrConstant<bb::fr>::from_index(index_for_read),
302 .value = WitnessOrConstant<bb::fr>::from_index(value_for_read) };
303 trace.push_back(mem_op);
304 break;
305 }
306 case AccessType::Write: {
307 const size_t ram_index_to_write = static_cast<size_t>(engine.get_random_uint32() % table_size);
308 const uint32_t index_to_write =
309 add_to_witness_and_track_indices(witness_values, bb::fr(ram_index_to_write));
310 bb::fr write_value = bb::fr::random_element();
311 const uint32_t value_to_write = add_to_witness_and_track_indices(witness_values, write_value);
312
313 // Update the table_values to reflect this write
314 table_values[ram_index_to_write] = write_value;
315
316 mem_op = { .access_type = AccessType::Write,
317 .index = WitnessOrConstant<bb::fr>::from_index(index_to_write),
318 .value = WitnessOrConstant<bb::fr>::from_index(value_to_write) };
319 trace.push_back(mem_op);
320 break;
321 }
322 }
323 }
324 if constexpr (perform_constant_ops) {
325 add_constant_ops<AccessType::Read>(table_size, table_values, witness_values, trace);
326 add_constant_ops<AccessType::Write>(table_size, table_values, witness_values, trace);
327 }
328
329 BB_ASSERT_EQ(read_values.size(), num_reads); // sanity check to make sure the number of reads is correct.
330 // Create the MemoryConstraint
331 }
332 memory_constraint = AcirConstraint{ .init = init_indices, .trace = trace, .type = BlockType::RAM };
333 }
334
335 void invalidate_witness([[maybe_unused]] AcirConstraint& memory_constraint,
336 WitnessVector& witness_values,
337 const InvalidWitness::Target& invalid_witness_target)
338 {
339 switch (invalid_witness_target) {
341 break;
343 if constexpr (num_reads > 0 && table_size > 0) {
344 // Tamper with a random read value using the recorded witness index
345 if (!read_values.empty()) {
346 const size_t random_read_idx = static_cast<size_t>(engine.get_random_uint32() % read_values.size());
347 const uint32_t witness_idx = read_values[random_read_idx].witness_index;
348 witness_values[witness_idx] += bb::fr(1);
349 }
350 }
351 break;
352 }
353 }
354};
355
356template <typename Params>
357class RAMTest : public ::testing::Test,
358 public TestClass<RAMTestingFunctions<typename Params::Builder,
359 Params::table_size,
360 Params::num_reads,
361 Params::num_writes,
362 Params::perform_constant_ops>> {
363 protected:
365};
366
367// Failure tests are impossible in the scenario with only writes.
369 testing::Types<RAMTestParams<UltraCircuitBuilder, 0, 0, 0, false>,
371 RAMTestParams<UltraCircuitBuilder, 10, 0, 0, true>, // Test the case in which there are only
372 // constant operations
380 RAMTestParams<MegaCircuitBuilder, 10, 0, 0, true>, // Test the case in which there are only
381 // constant operations
387
389
390TYPED_TEST(RAMTest, GenerateVKFromConstraints)
391{
392 using Flavor =
394 TestFixture::template test_vk_independence<Flavor>();
395}
396
398{
399 TestFixture::test_tampering();
400}
401
402template <CallDataType CallDataType_, size_t CallDataSize_, size_t NumReads_, bool PerformConstantOps_>
404 static constexpr CallDataType calldata_type = CallDataType_;
405 static constexpr size_t calldata_size = CallDataSize_;
406 static constexpr size_t num_reads = NumReads_;
407 static constexpr bool perform_constant_ops = PerformConstantOps_;
408};
409
410template <CallDataType calldata_type, size_t calldata_size, size_t num_reads, bool perform_constant_ops>
412 public:
415
417 public:
418 enum class Target : uint8_t { None, ReadValueIncremented };
419
421
422 static std::vector<std::string> get_labels() { return { "None", "ReadValueIncremented" }; };
423 };
424
425 void generate_constraints(AcirConstraint& memory_constraint, WitnessVector& witness_values)
426 {
427
428 // Create initial memory values "natively". Memory tables always start out initialized.
429 std::vector<bb::fr> calldata_values;
430 calldata_values.reserve(calldata_size);
431 for (size_t _i = 0; _i < calldata_size; _i++) {
432 calldata_values.push_back(bb::fr::random_element());
433 }
434
435 // `init_indices` contains the initial values of the circuit.
436 std::vector<uint32_t> init_indices;
437 for (size_t i = 0; i < calldata_size; ++i) {
438 uint32_t value_index = add_to_witness_and_track_indices(witness_values, calldata_values[i]);
439 init_indices.push_back(value_index);
440 }
441 // Initialize and create memory operations
443
444 // Add read operations
445 for (size_t idx = 0; idx < num_reads; ++idx) {
446 MemOp mem_op;
447 const size_t calldata_idx_to_read = static_cast<size_t>(engine.get_random_uint32() % calldata_size);
448 const uint32_t index_for_read =
449 add_to_witness_and_track_indices(witness_values, bb::fr(calldata_idx_to_read));
450 bb::fr read_value = calldata_values[calldata_idx_to_read];
451 const uint32_t value_for_read = add_to_witness_and_track_indices(witness_values, read_value);
452
453 mem_op = { .access_type = AccessType::Read,
454 .index = WitnessOrConstant<bb::fr>::from_index(index_for_read),
455 .value = WitnessOrConstant<bb::fr>::from_index(value_for_read) };
456 trace.push_back(mem_op);
457 }
458 if constexpr (perform_constant_ops) {
459 add_constant_ops<AccessType::Read>(calldata_size, calldata_values, witness_values, trace);
460 }
461
462 // Create the MemoryConstraint
463 memory_constraint = AcirConstraint{
464 .init = init_indices, .trace = trace, .type = BlockType::CallData, .calldata_id = calldata_type
465 };
466 }
467
468 void invalidate_witness(AcirConstraint& memory_constraint,
469 WitnessVector& witness_values,
470 const InvalidWitness::Target& invalid_witness_target)
471 {
472 switch (invalid_witness_target) {
474 break;
476 // Tamper with a random read value using the recorded witness index
477 const size_t random_read_idx = static_cast<size_t>(engine.get_random_uint32() % num_reads);
478 const uint32_t witness_idx = memory_constraint.trace[random_read_idx].index.index;
479 witness_values[witness_idx] += bb::fr(1);
480 break;
481 }
482 }
483};
484
485using CallDataTestConfigs = testing::Types<CallDataTestParams<CallDataType::Primary, 10, 5, false>,
487
488template <typename Params>
489class CallDataTests : public ::testing::Test,
490 public TestClass<CallDataTestingFunctions<Params::calldata_type,
491 Params::calldata_size,
492 Params::num_reads,
493 Params::perform_constant_ops>> {
494 protected:
496};
497
499
500TYPED_TEST(CallDataTests, GenerateVKFromConstraints)
501{
502 TestFixture::template test_vk_independence<MegaFlavor>();
503}
504
506{
507 TestFixture::test_tampering();
508}
509
510template <size_t returndata_size>
511
513 public:
516
517 // There is no tampering that can be done for ReturnData as the only thing that a return data opcode does is
518 // adding data to the return data bus vector and constraining such data to be equal to the data with which the
519 // memory operation was initialized
521 public:
522 enum class Target : uint8_t { None };
523
524 static std::vector<Target> get_all() { return { Target::None }; };
525
526 static std::vector<std::string> get_labels() { return { "None" }; };
527 };
528
529 void generate_constraints(AcirConstraint& memory_constraint, WitnessVector& witness_values)
530 {
531 // Create initial memory values "natively". Memory tables always start out initialized.
532 std::vector<bb::fr> returndata_values;
533 returndata_values.reserve(returndata_size);
534 for (size_t _i = 0; _i < returndata_size; _i++) {
535 returndata_values.push_back(bb::fr::random_element());
536 }
537
538 // `init_indices` contains the initial values of the circuit.
539 std::vector<uint32_t> init_indices;
540 for (size_t i = 0; i < returndata_size; ++i) {
541 uint32_t value_index = add_to_witness_and_track_indices(witness_values, returndata_values[i]);
542 init_indices.push_back(value_index);
543 }
544
545 // Create the MemoryConstraint
546 memory_constraint = AcirConstraint{ .init = init_indices, .trace = {}, .type = BlockType::ReturnData };
547 }
548
549 void invalidate_witness([[maybe_unused]] AcirConstraint& memory_constraint,
550 [[maybe_unused]] WitnessVector& witness_values,
551 const InvalidWitness::Target& invalid_witness_target)
552 {
553 switch (invalid_witness_target) {
555 break;
556 }
557 }
558};
559
560static constexpr size_t RETURNDATA_SIZE = 10;
561class ReturnDataTests : public ::testing::Test, public TestClass<ReturnDataTestingFunctions<RETURNDATA_SIZE>> {
562 protected:
564};
565
566TEST_F(ReturnDataTests, GenerateVKFromConstraints)
567{
568 test_vk_independence<MegaFlavor>();
569}
570
571template <typename Builder> class EmptyBlockConstraintTests : public ::testing::Test {};
572
573using BuilderTypes = testing::Types<UltraCircuitBuilder, MegaCircuitBuilder>;
575
576// Test that all block constraint types handle empty initialization gracefully
578{
579 using Builder = TypeParam;
580
582
583 // Test each block constraint type
584 if constexpr (IsUltraBuilder<Builder>) {
585 types_to_test = { { BlockType::ROM, CallDataType::None }, { BlockType::RAM, CallDataType::None } };
586 } else {
587 types_to_test = { { BlockType::ROM, CallDataType::None },
588 { BlockType::RAM, CallDataType::None },
589 { BlockType::CallData, CallDataType::Primary },
590 { BlockType::CallData, CallDataType::Secondary },
591 { BlockType::ReturnData, CallDataType::None } };
592 }
593
594 // Create empty block constraint
595 for (auto type : types_to_test) {
596 BlockConstraint block{
597 .init = {}, // Empty initialization data
598 .trace = {}, // Empty trace
599 .type = type.first,
600 .calldata_id = type.second,
601 };
602
603 AcirProgram program;
604 program.constraints = {
605 .num_acir_opcodes = 1,
606 .public_inputs = {},
607 .block_constraints = { block },
608 .original_opcode_indices = create_empty_original_opcode_indices(),
609 };
610
612
613 // Circuit construction should succeed without errors
614 auto circuit = create_circuit<Builder>(program);
615 EXPECT_TRUE(CircuitChecker::check(circuit));
616 }
617}
acir_format::AcirFormatOriginalOpcodeIndices create_empty_original_opcode_indices()
void mock_opcode_indices(acir_format::AcirFormat &constraint_system)
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:77
testing::Types< ROMTestParams< UltraCircuitBuilder, 0, 0, false >, ROMTestParams< UltraCircuitBuilder, 10, 0, false >, ROMTestParams< UltraCircuitBuilder, 10, 0, true >, ROMTestParams< MegaCircuitBuilder, 10, 10, true >, ROMTestParams< MegaCircuitBuilder, 10, 20, true > > ROMTestConfigs
testing::Types< RAMTestParams< UltraCircuitBuilder, 0, 0, 0, false >, RAMTestParams< UltraCircuitBuilder, 10, 0, 0, false >, RAMTestParams< UltraCircuitBuilder, 10, 0, 0, true >, RAMTestParams< UltraCircuitBuilder, 10, 0, 10, false >, RAMTestParams< UltraCircuitBuilder, 10, 0, 10, true >, RAMTestParams< UltraCircuitBuilder, 10, 10, 0, false >, RAMTestParams< UltraCircuitBuilder, 10, 10, 0, true >, RAMTestParams< UltraCircuitBuilder, 10, 20, 10, true >, RAMTestParams< MegaCircuitBuilder, 0, 0, 0, false >, RAMTestParams< MegaCircuitBuilder, 10, 0, 0, false >, RAMTestParams< MegaCircuitBuilder, 10, 0, 0, true >, RAMTestParams< MegaCircuitBuilder, 10, 0, 10, false >, RAMTestParams< MegaCircuitBuilder, 10, 0, 10, true >, RAMTestParams< MegaCircuitBuilder, 10, 10, 0, false >, RAMTestParams< MegaCircuitBuilder, 10, 10, 0, true >, RAMTestParams< MegaCircuitBuilder, 10, 20, 10, true > > RAMTestConfigs
testing::Types< CallDataTestParams< CallDataType::Primary, 10, 5, false >, CallDataTestParams< CallDataType::Primary, 10, 5, true > > CallDataTestConfigs
void add_constant_ops(const size_t table_size, const std::vector< bb::fr > &table_values, WitnessVector &witness_values, std::vector< MemOp > &trace)
Utility method to add read/write operations with constant indices/values.
static std::vector< std::string > get_labels()
void generate_constraints(AcirConstraint &memory_constraint, WitnessVector &witness_values)
void invalidate_witness(AcirConstraint &memory_constraint, WitnessVector &witness_values, const InvalidWitness::Target &invalid_witness_target)
static void SetUpTestSuite()
static void SetUpTestSuite()
static std::vector< Target > get_all()
static std::vector< std::string > get_labels()
void invalidate_witness(AcirConstraint &memory_constraint, WitnessVector &witness_values, const InvalidWitness::Target &invalid_witness_target)
void generate_constraints(AcirConstraint &memory_constraint, WitnessVector &witness_values)
std::vector< WitnessValue > read_values
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 void invalidate_witness(AcirConstraint &memory_constraint, WitnessVector &witness_values, const InvalidWitness::Target &invalid_witness_target)
static std::vector< std::string > get_labels()
void invalidate_witness(AcirConstraint &memory_constraint, WitnessVector &witness_values, const InvalidWitness::Target &invalid_witness_target)
void generate_constraints(AcirConstraint &memory_constraint, WitnessVector &witness_values)
static bool check(const Builder &circuit)
Check the witness satisifies the circuit.
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(WitnessVector &witness, const T &input)
Append values to a witness vector and track their indices.
Definition utils.hpp:70
RNG & get_debug_randomness(bool reset, std::uint_fast64_t seed)
Definition engine.cpp:190
std::filesystem::path bb_crs_path()
void init_file_crs_factory(const std::filesystem::path &path)
TEST_F(BoomerangGoblinRecursiveVerifierTests, graph_description_basic)
Construct and check a goblin recursive verification circuit.
TYPED_TEST_SUITE(BoomerangRecursiveVerifierTest, Flavors)
field< Bn254FrParams > fr
Definition fr.hpp:174
TYPED_TEST(ShpleminiTest, CorrectnessOfMultivariateClaimBatching)
MegaCircuitBuilder_< field< Bn254FrParams > > MegaCircuitBuilder
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
::testing::Types< UltraCircuitBuilder, MegaCircuitBuilder > BuilderTypes
static constexpr CallDataType calldata_type
static constexpr size_t calldata_size
static constexpr bool perform_constant_ops
static constexpr size_t num_reads
static constexpr bool perform_constant_ops
static constexpr size_t num_reads
static constexpr size_t num_writes
static constexpr size_t table_size
static constexpr bool perform_constant_ops
static constexpr size_t table_size
static constexpr size_t num_reads
Struct holding the data required to add memory constraints to a circuit.
std::vector< uint32_t > init
Memory operation. Index and value store the index of the memory location, and value is the value to b...
static WitnessOrConstant from_index(uint32_t index)
static WitnessOrConstant from_constant(FF value)
static field random_element(numeric::RNG *engine=nullptr) noexcept