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
311 AcirFormat acir_format = constraint_to_acir_format(recursion_constraint);
312
313 AcirProgram acir_program{ .constraints = acir_format, .witness = witnesses };
314
315 std::tie(constraints[idx], witness_vectors[idx]) = [&]() {
316 auto builder = create_circuit<InnerBuilder>(acir_program, generate_metadata());
318 }();
319 }
320 }
321 }
322
323 // Merge the constraints by offsetting the relevant indices and concatenating the witness vectors
324 honk_recursion_constraint = merge_recursion_constraints(constraints, witness_vectors, witness_values);
325 }
326};
327
328template <typename Params>
330 : public ::testing::Test,
331 public TestClassWithPredicate<HonkRecursionConstraintTestingFunctions<typename Params::RecursiveFlavor,
332 Params::IsRootRollup,
333 Params::N,
334 Params::LayerSizes>> {
335 public:
336 using RecursiveFlavor = Params::RecursiveFlavor;
337 static constexpr bool IsRootRollup = Params::IsRootRollup;
338
339 protected:
341};
342
343// We test the predicate with vanilla recursion. This is enough as the predicate logic is a standalone component,
344// there's no inter-constraint interaction due to the existence or the value of the predicate.
346 testing::Types<HonkRecursionTestParams<UltraRecursiveFlavor_<UltraCircuitBuilder>, false, 1, { 1 }>,
351
353
355{
356 // The flavor with which we prove the outer circuit (the one verifying F_1, .., F_{s_1}) depends on what type of
357 // data the inner circuits have propagated and the builder.
360 typename TestFixture::RecursiveFlavor::NativeFlavor>;
361 TestFixture::template test_vk_independence<Flavor>();
362}
363
365{
367 TestFixture::test_constant_true(TestFixture::InvalidWitnessTarget::VKHash);
368}
369
371{
373 TestFixture::test_witness_true(TestFixture::InvalidWitnessTarget::VKHash);
374}
375
377{
379 TestFixture::test_witness_false_slow();
380}
381
383{
384 using RecursiveFlavor = TestFixture::RecursiveFlavor;
385 using Builder = TestFixture::Builder;
386 using InvalidWitnessTarget = TestFixture::InvalidWitnessTarget;
387
388 typename TestFixture::AcirConstraint constraint;
389 WitnessVector witness_values;
390 TestFixture::Base::generate_constraints(constraint, witness_values);
391
392 for (const auto predicate_mode : Predicate<InvalidWitnessTarget>::get_all()) {
393 Predicate<InvalidWitnessTarget> predicate{ .test_case = predicate_mode,
394 .invalid_witness = InvalidWitnessTarget::None };
395 auto [updated_constraint, updated_witness_values] =
396 TestFixture::update_witness_based_on_predicate(constraint, witness_values, predicate);
397
398 AcirFormat constraint_system = constraint_to_acir_format(updated_constraint);
399
400 AcirProgram program{ constraint_system, updated_witness_values };
401 ProgramMetadata metadata = TestFixture::Base::generate_metadata();
402 metadata.collect_gates_per_opcode = true;
403 auto builder = create_circuit<Builder>(program, metadata);
404
405 // Verify the gate count was recorded
406 EXPECT_EQ(program.constraints.gates_per_opcode.size(), 1);
407
408 // Get expected values based on predicate mode
409 auto [EXPECTED_GATE_COUNT, EXPECTED_ULTRA_OPS] = HONK_RECURSION_CONSTANTS<RecursiveFlavor>(predicate_mode);
410
411 // Assert gate count
412 EXPECT_EQ(program.constraints.gates_per_opcode[0], EXPECTED_GATE_COUNT);
413
414 // For MegaBuilder, also assert ultra ops count
415 if constexpr (IsMegaBuilder<Builder>) {
416 size_t actual_ultra_ops = builder.op_queue->get_current_subtable_size();
417 EXPECT_EQ(actual_ultra_ops, EXPECTED_ULTRA_OPS);
418 }
419 }
420}
421
422template <typename Params>
424 : public ::testing::Test,
425 public TestClass<HonkRecursionConstraintTestingFunctions<typename Params::RecursiveFlavor,
426 Params::IsRootRollup,
427 Params::N,
428 Params::LayerSizes>> {
429 public:
430 using RecursiveFlavor = Params::RecursiveFlavor;
431 static constexpr bool IsRootRollup = Params::IsRootRollup;
432
433 protected:
435};
436
439 // circuit
441 // circuit
443 // circuit
445 HonkRecursionTestParams<UltraZKRecursiveFlavor_<UltraCircuitBuilder>, false, 2, { 2, 1 }>, // Double recursion on
446 // one side
447 HonkRecursionTestParams<UltraZKRecursiveFlavor_<UltraCircuitBuilder>, false, 2, { 2, 2 }>, // Merge two circuits
448 // that recursively
449 // verify two circuits
450 HonkRecursionTestParams<UltraRecursiveFlavor_<MegaCircuitBuilder>, false, 4, { 4, 3, 1, 1 }>>; // Random complex
451 // flow
452
454
456{
457 if constexpr (TypeParam::IsRootRollup) {
458 TestFixture::template test_vk_independence<UltraZKFlavor>();
459 } else {
460 // The flavor with which we prove the outer circuit (the one verifying F_1, .., F_{s_1}) depends on what type of
461 // data the inner circuits have propagated and the builder.
464 typename TestFixture::RecursiveFlavor::NativeFlavor>;
465
466 TestFixture::template test_vk_independence<Flavor>();
467 }
468}
469
471{
473 [[maybe_unused]] std::vector<std::string> _ = TestFixture::test_tampering();
474}
475
477{
478 using Builder = TestFixture::Builder;
479
480 if constexpr (!TestFixture::IsRootRollup) {
481 GTEST_SKIP(); // We have already pinned the gate counts in this situation
482 }
483
484 typename TestFixture::AcirConstraint constraint;
485 WitnessVector witness_values;
486 TestFixture::Base::generate_constraints(constraint, witness_values);
487
488 AcirFormat constraint_system = constraint_to_acir_format(constraint);
489
490 AcirProgram program{ constraint_system, witness_values };
491 ProgramMetadata metadata = TestFixture::Base::generate_metadata();
492 metadata.collect_gates_per_opcode = true;
493 auto builder = create_circuit<Builder>(program, metadata);
494
495 // Verify the gate count was recorded
496 EXPECT_EQ(program.constraints.gates_per_opcode.size(), 2);
497
498 // Assert gate count
499 EXPECT_EQ(builder.get_num_finalized_gates_inefficient(), ROOT_ROLLUP_GATE_COUNT);
500}
#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)
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(CommitmentKeyTest, CommitToZeroPoly)
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()