Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
honk_recursion_constraint.test.cpp
Go to the documentation of this file.
2#include "acir_format.hpp"
13
14#include <gtest/gtest.h>
15#include <vector>
16
17using namespace acir_format;
18using namespace bb;
19
54template <typename RecursiveFlavor_, bool IsRootRollup_, size_t N_, std::array<size_t, N_> LayerSizes_>
56 public:
57 using RecursiveFlavor = RecursiveFlavor_;
58 static constexpr bool IsRootRollup = IsRootRollup_;
59 static constexpr size_t N = N_;
60 static constexpr std::array<size_t, N> LayerSizes = LayerSizes_;
61};
62
63template <typename RecursiveFlavor, bool IsRootRollup, size_t N, std::array<size_t, N> LayerSizes>
65 public:
66 // Check that the array in the template parameter is in increasing order
67 static_assert([]() {
68 for (size_t idx = 0; idx < N - 1; idx++) {
69 if (LayerSizes[idx + 1] > LayerSizes[idx]) {
70 return false;
71 }
72 }
73
74 return true;
75 });
76
77 // Check that if IsRootRollup is true, then we set the parameters correctly
78 static_assert([]() {
79 if constexpr (IsRootRollup) {
80 return HasIPAAccumulator<RecursiveFlavor> && N == 1 && LayerSizes[0] == 2;
81 }
82
83 return true;
84 });
85
86 using InnerFlavor = RecursiveFlavor::NativeFlavor;
87 using InnerBuilder = InnerFlavor::CircuitBuilder;
92 using InnerVerificationKey = InnerFlavor::VerificationKey;
94
95 // Determine the proof type of the "inner" circuits, i.e., the ones up to the level below the top. The proof type is
96 // determined by the flavor with which we prove the circuits.
97 static constexpr uint32_t InnerProofType = []() {
98 if constexpr (HasIPAAccumulator<InnerFlavor>) {
99 return ROLLUP_HONK;
100 } else if constexpr (InnerFlavor::HasZK) {
101 return HONK_ZK;
102 }
103
104 return HONK;
105 }();
106
107 static constexpr bool IS_SINGLE_RECURSIVE_VERIFICATION = N == 1 && LayerSizes[0] == 1;
110 using Builder = RecursiveFlavor::CircuitBuilder;
111
112 // All the circuits have the same number of public inputs, this tests the slicing of public inputs from the proof at
113 // every level
114 static constexpr size_t NUM_PUBLIC_INPUTS = 2;
115
117 public:
118 enum class Target : uint8_t { None, VKHash, VK, Proof };
119
121 {
122 if constexpr (IsRootRollup) {
123 // Only one for Root because it is very heavy
124 return { Target::VKHash };
125 }
127 }
128
129 static std::vector<std::string> get_labels()
130 {
131 if constexpr (IsRootRollup) {
132 // Only one for Root because it is very heavy
133 return { "VKHash" };
134 }
135 return { "None", "VKHash", "VK", "Proof" };
136 }
137 };
138
145 {
147
150
151 for (size_t idx = 0; idx < NUM_PUBLIC_INPUTS; idx++) {
152 builder.add_public_variable(InnerBuilder::FF::random_element());
153 }
154 InnerIO::add_default(builder);
155
156 return builder;
157 }
158
169 std::vector<WitnessVector>& witness_vectors,
170 WitnessVector& witness_values)
171 {
172 // Lambda to offset the recursion constraint by the current size of the witness vector
173 auto offset_recursion_constraint = [](RecursionConstraint& honk_recursion_constraint, const size_t offset) {
174 auto shift_by_offset = [&offset](std::vector<uint32_t>& indices) {
175 for (auto& witness_idx : indices) {
176 witness_idx += offset;
177 }
178 };
179
180 shift_by_offset(honk_recursion_constraint.key);
181 shift_by_offset(honk_recursion_constraint.proof);
182 shift_by_offset(honk_recursion_constraint.public_inputs);
183 honk_recursion_constraint.key_hash += offset;
184 honk_recursion_constraint.predicate.index += offset;
185 };
186
187 for (auto [constraint, witnesses] : zip_view(constraints, witness_vectors)) {
188 offset_recursion_constraint(constraint, witness_values.size());
189 // If this is the root rollup, we need to set the proof type to ROOT_ROLLUP_HONK
190 constraint.proof_type = IsRootRollup ? ROOT_ROLLUP_HONK : constraint.proof_type;
191 witness_values.insert(witness_values.end(), witnesses.begin(), witnesses.end());
192 }
193
194 if constexpr (IS_SINGLE_RECURSIVE_VERIFICATION) {
195 return constraints[0];
196 } else {
197 return constraints;
198 }
199 }
200
207 {
208 // Add public inputs if needed
209 for (size_t idx = builder.num_public_inputs(); idx < NUM_PUBLIC_INPUTS; idx++) {
210 builder.add_public_variable(InnerBuilder::FF::random_element());
211 }
212 auto prover_instance = std::make_shared<InnerProverInstance>(builder);
213 auto verification_key = std::make_shared<InnerVerificationKey>(prover_instance->get_precomputed());
214
215 InnerProver prover(prover_instance, verification_key);
216 auto proof = prover.construct_proof();
217
218 WitnessVector witness_values;
219 RecursionConstraint recursion_constraint =
221 proof,
222 verification_key->to_field_elements(),
223 verification_key->hash(),
224 bb::fr::one(),
225 builder.num_public_inputs() - InnerIO::PUBLIC_INPUTS_SIZE,
227
228 return { recursion_constraint, witness_values };
229 }
230
236 {
237 return ProgramMetadata{ .has_ipa_claim = HasIPAAccumulator<RecursiveFlavor> && !IsRootRollup };
238 }
239
241 AcirConstraint honk_recursion_constraints,
242 WitnessVector witness_values,
243 const InvalidWitness::Target& invalid_witness_target)
244 {
245 switch (invalid_witness_target) {
247 break;
249 // Invalidate the circuit by modifying the vk hash
250 if constexpr (IS_SINGLE_RECURSIVE_VERIFICATION) {
251 witness_values[honk_recursion_constraints.key_hash] += fr::one();
252 } else {
253 witness_values[honk_recursion_constraints[0].key_hash] += fr::one();
254 }
255 break;
256 }
258 // Invalidate the circuit by modifying the first element of the vk (log circuit size)
259 std::vector<uint32_t> vk_indices;
260 if constexpr (IS_SINGLE_RECURSIVE_VERIFICATION) {
261 witness_values[honk_recursion_constraints.key[0]] += fr::one();
262 } else {
263 witness_values[honk_recursion_constraints[0].key[0]] += fr::one();
264 }
265 break;
266 }
268 // Invalidate the circuit by modifying the first element of the proof after the public inputs, which is a
269 // group element (vk commitment)
270 std::vector<uint32_t> proof_indices;
271 if constexpr (IS_SINGLE_RECURSIVE_VERIFICATION) {
272 proof_indices = honk_recursion_constraints.proof;
273 } else {
274 proof_indices = honk_recursion_constraints[0].proof;
275 }
276 size_t commitment_size = FrCodec::template calc_num_fields<typename InnerFlavor::Commitment>();
277 std::vector<bb::fr> mock_proof_element = FrCodec::serialize_to_fields(InnerFlavor::Commitment::one());
278 for (size_t idx = 0; idx < commitment_size; idx++) {
279 witness_values[proof_indices[InnerIO::PUBLIC_INPUTS_SIZE + idx]] = mock_proof_element[idx];
280 }
281 break;
282 }
283 }
284
285 return { honk_recursion_constraints, witness_values };
286 }
287
288 static void generate_constraints(AcirConstraint& honk_recursion_constraint, WitnessVector& witness_values)
289 {
291 std::vector<WitnessVector> witness_vectors;
292
293 for (size_t layer_idx = 0; layer_idx < N; layer_idx++) {
294 size_t current_layer_size = LayerSizes[layer_idx];
295 for (size_t idx = 0; idx < current_layer_size; idx++) {
296 if (layer_idx == 0) {
297 // If we are at the bottom layer, we create the circuit and then create the recursion constraint
298 // that verifies the circuit
299 auto [constraint, witnesses] = []() {
302 }();
303 constraints.emplace_back(std::move(constraint));
304 witness_vectors.emplace_back(std::move(witnesses));
305 } else {
306 // If we are in a layer above the bottom one, we take the recursion constraints at index idx and
307 // build a circuit the recursively verifies these recursion constraints
308 RecursionConstraint recursion_constraint = constraints[idx];
309 WitnessVector witnesses = witness_vectors[idx];
310
312 recursion_constraint, /*max_witness_index=*/static_cast<uint32_t>(witnesses.size()) - 1);
313
314 AcirProgram acir_program{ .constraints = acir_format, .witness = witnesses };
315
316 std::tie(constraints[idx], witness_vectors[idx]) = [&]() {
317 auto builder = create_circuit<InnerBuilder>(acir_program, generate_metadata());
319 }();
320 }
321 }
322 }
323
324 // Merge the constraints by offsetting the relevant indices and concatenating the witness vectors
325 honk_recursion_constraint = merge_recursion_constraints(constraints, witness_vectors, witness_values);
326 }
327};
328
329template <typename Params>
331 : public ::testing::Test,
332 public TestClassWithPredicate<HonkRecursionConstraintTestingFunctions<typename Params::RecursiveFlavor,
333 Params::IsRootRollup,
334 Params::N,
335 Params::LayerSizes>> {
336 public:
337 using RecursiveFlavor = Params::RecursiveFlavor;
338 static constexpr bool IsRootRollup = Params::IsRootRollup;
339
340 protected:
342};
343
344// We test the predicate with vanilla recursion. This is enough as the predicate logic is a standalone component,
345// there's no inter-constraint interaction due to the existence or the value of the predicate.
347 testing::Types<HonkRecursionTestParams<UltraRecursiveFlavor_<UltraCircuitBuilder>, false, 1, { 1 }>,
352
354
356{
357 // The flavor with which we prove the outer circuit (the one verifying F_1, .., F_{s_1}) depends on what type of
358 // data the inner circuits have propagated and the builder.
361 typename TestFixture::RecursiveFlavor::NativeFlavor>;
362 TestFixture::template test_vk_independence<Flavor>();
363}
364
366{
368 TestFixture::test_constant_true(TestFixture::InvalidWitnessTarget::VKHash);
369}
370
372{
374 TestFixture::test_witness_true(TestFixture::InvalidWitnessTarget::VKHash);
375}
376
378{
380 TestFixture::test_witness_false_slow();
381}
382
384{
385 using RecursiveFlavor = TestFixture::RecursiveFlavor;
386 using Builder = TestFixture::Builder;
387 using InvalidWitnessTarget = TestFixture::InvalidWitnessTarget;
388
389 typename TestFixture::AcirConstraint constraint;
390 WitnessVector witness_values;
391 TestFixture::Base::generate_constraints(constraint, witness_values);
392
393 for (const auto predicate_mode : Predicate<InvalidWitnessTarget>::get_all()) {
394 Predicate<InvalidWitnessTarget> predicate{ .test_case = predicate_mode,
395 .invalid_witness = InvalidWitnessTarget::None };
396 auto [updated_constraint, updated_witness_values] =
397 TestFixture::update_witness_based_on_predicate(constraint, witness_values, predicate);
398
399 AcirFormat constraint_system = constraint_to_acir_format(
400 updated_constraint, /*max_witness_index=*/static_cast<uint32_t>(updated_witness_values.size()) - 1);
401
402 AcirProgram program{ constraint_system, updated_witness_values };
403 ProgramMetadata metadata = TestFixture::Base::generate_metadata();
404 metadata.collect_gates_per_opcode = true;
405 auto builder = create_circuit<Builder>(program, metadata);
406
407 // Verify the gate count was recorded
408 EXPECT_EQ(program.constraints.gates_per_opcode.size(), 1);
409
410 // Get expected values based on predicate mode
411 auto [EXPECTED_GATE_COUNT, EXPECTED_ULTRA_OPS] = HONK_RECURSION_CONSTANTS<RecursiveFlavor>(predicate_mode);
412
413 // Assert gate count
414 EXPECT_EQ(program.constraints.gates_per_opcode[0], EXPECTED_GATE_COUNT);
415
416 // For MegaBuilder, also assert ultra ops count
417 if constexpr (IsMegaBuilder<Builder>) {
418 size_t actual_ultra_ops = builder.op_queue->get_current_subtable_size();
419 EXPECT_EQ(actual_ultra_ops, EXPECTED_ULTRA_OPS);
420 }
421 }
422}
423
424template <typename Params>
426 : public ::testing::Test,
427 public TestClass<HonkRecursionConstraintTestingFunctions<typename Params::RecursiveFlavor,
428 Params::IsRootRollup,
429 Params::N,
430 Params::LayerSizes>> {
431 public:
432 using RecursiveFlavor = Params::RecursiveFlavor;
433 static constexpr bool IsRootRollup = Params::IsRootRollup;
434
435 protected:
437};
438
441 // circuit
443 // circuit
445 // circuit
447 HonkRecursionTestParams<UltraZKRecursiveFlavor_<UltraCircuitBuilder>, false, 2, { 2, 1 }>, // Double recursion on
448 // one side
449 HonkRecursionTestParams<UltraZKRecursiveFlavor_<UltraCircuitBuilder>, false, 2, { 2, 2 }>, // Merge two circuits
450 // that recursively
451 // verify two circuits
452 HonkRecursionTestParams<UltraRecursiveFlavor_<MegaCircuitBuilder>, false, 4, { 4, 3, 1, 1 }>>; // Random complex
453 // flow
454
456
458{
459 if constexpr (TypeParam::IsRootRollup) {
460 TestFixture::template test_vk_independence<UltraZKFlavor>();
461 } else {
462 // The flavor with which we prove the outer circuit (the one verifying F_1, .., F_{s_1}) depends on what type of
463 // data the inner circuits have propagated and the builder.
466 typename TestFixture::RecursiveFlavor::NativeFlavor>;
467
468 TestFixture::template test_vk_independence<Flavor>();
469 }
470}
471
473{
475 [[maybe_unused]] std::vector<std::string> _ = TestFixture::test_tampering();
476}
477
479{
480 using Builder = TestFixture::Builder;
481
482 if constexpr (!TestFixture::IsRootRollup) {
483 GTEST_SKIP(); // We have already pinned the gate counts in this situation
484 }
485
486 typename TestFixture::AcirConstraint constraint;
487 WitnessVector witness_values;
488 TestFixture::Base::generate_constraints(constraint, witness_values);
489
490 AcirFormat constraint_system =
491 constraint_to_acir_format(constraint, /*max_witness_index=*/static_cast<uint32_t>(witness_values.size()) - 1);
492
493 AcirProgram program{ constraint_system, witness_values };
494 ProgramMetadata metadata = TestFixture::Base::generate_metadata();
495 metadata.collect_gates_per_opcode = true;
496 auto builder = create_circuit<Builder>(program, metadata);
497
498 // Verify the gate count was recorded
499 EXPECT_EQ(program.constraints.gates_per_opcode.size(), 2);
500
501 // Assert gate count
502 EXPECT_EQ(builder.get_num_finalized_gates_inefficient(), ROOT_ROLLUP_GATE_COUNT);
503}
#define BB_DISABLE_ASSERTS()
Definition assert.hpp:33
std::conditional_t< IS_SINGLE_RECURSIVE_VERIFICATION, RecursionConstraint, std::vector< RecursionConstraint > > AcirConstraint
static InnerBuilder create_inner_circuit()
Create a dummy circuit.
static void generate_constraints(AcirConstraint &honk_recursion_constraint, WitnessVector &witness_values)
std::conditional_t< HasIPAAccumulator< InnerFlavor >, bb::stdlib::recursion::honk::RollupIO, bb::stdlib::recursion::honk::DefaultIO< InnerBuilder > > InnerIO
static ProgramMetadata generate_metadata()
Generate the metadata for the circuit recursively verifying the top layer circuits.
static std::pair< AcirConstraint, WitnessVector > invalidate_witness(AcirConstraint honk_recursion_constraints, WitnessVector witness_values, const InvalidWitness::Target &invalid_witness_target)
static std::pair< RecursionConstraint, WitnessVector > circuit_to_recursion_constraint(InnerBuilder &builder)
Convert a circuit into a recursion constraint: prove the circuit and add the proof,...
static AcirConstraint merge_recursion_constraints(std::vector< RecursionConstraint > &constraints, std::vector< WitnessVector > &witness_vectors, WitnessVector &witness_values)
Merge a series of recursion constraints by offsetting the relevant indices and concatenating the witn...
static constexpr std::array< size_t, N > LayerSizes
Test class for ACIR constraints that contain a predicate.
static std::vector< fr > serialize_to_fields(const T &val)
Conversion from transcript values to bb::frs.
static void add_lookup_gates(Builder &builder, size_t num_iterations=1)
Add lookup gates using the uint32 XOR lookup table (table size 4096)
static void add_arithmetic_gates(Builder &builder, const size_t num_gates=4)
Add a specified number of arithmetic gates to the provided circuit.
A ProverInstance is normally constructed from a finalized circuit and it contains all the information...
A simple wrapper around a vector of stdlib field elements representing a proof.
Definition proof.hpp:19
Manages the data that is propagated on the public inputs of an application/function circuit.
The data that is propagated on the public inputs of a rollup circuit.
AluTraceBuilder builder
Definition alu.test.cpp:124
ssize_t offset
Definition engine.cpp:36
testing::Types< HonkRecursionTestParams< UltraRecursiveFlavor_< UltraCircuitBuilder >, false, 1, { 1 }>, HonkRecursionTestParams< UltraZKRecursiveFlavor_< UltraCircuitBuilder >, false, 1, { 1 }>, HonkRecursionTestParams< UltraRollupRecursiveFlavor_< UltraCircuitBuilder >, false, 1, { 1 }>, HonkRecursionTestParams< UltraRecursiveFlavor_< MegaCircuitBuilder >, false, 1, { 1 }>, HonkRecursionTestParams< UltraZKRecursiveFlavor_< MegaCircuitBuilder >, false, 1, { 1 }> > HonkRecursionTypesWithPredicate
testing::Types< HonkRecursionTestParams< UltraRecursiveFlavor_< UltraCircuitBuilder >, false, 1, { 2 }>, HonkRecursionTestParams< UltraZKRecursiveFlavor_< UltraCircuitBuilder >, false, 1, { 2 }>, HonkRecursionTestParams< UltraRollupRecursiveFlavor_< UltraCircuitBuilder >, false, 1, { 2 }>, HonkRecursionTestParams< UltraRollupRecursiveFlavor_< UltraCircuitBuilder >, true, 1, { 2 }>, HonkRecursionTestParams< UltraZKRecursiveFlavor_< UltraCircuitBuilder >, false, 2, { 2, 1 }>, HonkRecursionTestParams< UltraZKRecursiveFlavor_< UltraCircuitBuilder >, false, 2, { 2, 2 }>, HonkRecursionTestParams< UltraRecursiveFlavor_< MegaCircuitBuilder >, false, 4, { 4, 3, 1, 1 }> > HonkRecursionTypesWithoutPredicate
AcirFormat constraint_to_acir_format(const ConstraintType &constraint, uint32_t varnum)
Convert an AcirConstraint (single or vector) to AcirFormat by going through the full ACIR serde flow.
RecursionConstraint recursion_data_to_recursion_constraint(std::vector< bb::fr > &witness, const std::vector< bb::fr > &proof, const std::vector< bb::fr > &key, const bb::fr &key_hash, const bb::fr &predicate, const size_t num_public_inputs_to_extract, const uint32_t proof_type)
========== TESTING UTILITIES ========== ///
Definition utils.cpp:53
std::vector< bb::fr > WitnessVector
constexpr size_t ROOT_ROLLUP_GATE_COUNT
std::filesystem::path bb_crs_path()
void init_file_crs_factory(const std::filesystem::path &path)
TYPED_TEST_SUITE(BoomerangRecursiveVerifierTest, Flavors)
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
TYPED_TEST(ShpleminiTest, CorrectnessOfMultivariateClaimBatching)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
Barretenberg's representation of ACIR constraints.
Struct containing both the constraints to be added to the circuit and the witness vector.
static std::vector< PredicateTestCase > get_all()
Metadata required to create a circuit.
RecursionConstraint struct contains information required to recursively verify a proof.
WitnessOrConstant< bb::fr > predicate
static constexpr field one()