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 // Root rollup requires N == 1 && LayerSizes[0] == 2 for 2 recursive verifications
81 return N == 1 && LayerSizes[0] == 2;
82 }
83
84 return true;
85 }());
86
87 using InnerFlavor = RecursiveFlavor::NativeFlavor;
88 using InnerBuilder = InnerFlavor::CircuitBuilder;
89 // For rollup tests, we use RollupIO; otherwise we use DefaultIO
90 static constexpr bool UseRollupIO = IsRootRollup;
95 using InnerVerificationKey = InnerFlavor::VerificationKey;
97
98 // Determine the proof type of the "inner" circuits, i.e., the ones up to the level below the top. The proof type is
99 // determined by the flavor with which we prove the circuits.
100 static constexpr uint32_t InnerProofType = []() {
101 if constexpr (UseRollupIO) {
102 return ROLLUP_HONK;
103 } else if constexpr (InnerFlavor::HasZK) {
104 return HONK_ZK;
105 }
106
107 return HONK;
108 }();
109
110 static constexpr bool IS_SINGLE_RECURSIVE_VERIFICATION = N == 1 && LayerSizes[0] == 1;
113 using Builder = RecursiveFlavor::CircuitBuilder;
114
115 // All the circuits have the same number of public inputs, this tests the slicing of public inputs from the proof at
116 // every level
117 static constexpr size_t NUM_PUBLIC_INPUTS = 2;
118
120 public:
121 enum class Target : uint8_t { None, VKHash, VK, Proof };
122
124 {
125 if constexpr (IsRootRollup) {
126 // Only one for Root because it is very heavy
127 return { Target::VKHash };
128 }
130 }
131
132 static std::vector<std::string> get_labels()
133 {
134 if constexpr (IsRootRollup) {
135 // Only one for Root because it is very heavy
136 return { "VKHash" };
137 }
138 return { "None", "VKHash", "VK", "Proof" };
139 }
140 };
141
148 {
150
153
154 for (size_t idx = 0; idx < NUM_PUBLIC_INPUTS; idx++) {
155 builder.add_public_variable(InnerBuilder::FF::random_element());
156 }
157 InnerIO::add_default(builder);
158
159 return builder;
160 }
161
172 std::vector<WitnessVector>& witness_vectors,
173 WitnessVector& witness_values)
174 {
175 // Lambda to offset the recursion constraint by the current size of the witness vector
176 auto offset_recursion_constraint = [](RecursionConstraint& honk_recursion_constraint, const size_t offset) {
177 uint32_t uint32_offset = static_cast<uint32_t>(offset);
178 auto shift_by_offset = [&uint32_offset](std::vector<uint32_t>& indices) {
179 for (auto& index : indices) {
180 index += uint32_offset;
181 }
182 };
183
184 shift_by_offset(honk_recursion_constraint.key);
185 shift_by_offset(honk_recursion_constraint.proof);
186 shift_by_offset(honk_recursion_constraint.public_inputs);
187 honk_recursion_constraint.key_hash += uint32_offset;
188 honk_recursion_constraint.predicate.index += uint32_offset;
189 };
190
191 for (auto [constraint, witnesses] : zip_view(constraints, witness_vectors)) {
192 offset_recursion_constraint(constraint, witness_values.size());
193 // If this is the root rollup, we need to set the proof type to ROOT_ROLLUP_HONK
194 constraint.proof_type = IsRootRollup ? static_cast<uint32_t>(ROOT_ROLLUP_HONK) : constraint.proof_type;
195 witness_values.insert(witness_values.end(), witnesses.begin(), witnesses.end());
196 }
197
198 if constexpr (IS_SINGLE_RECURSIVE_VERIFICATION) {
199 return constraints[0];
200 } else {
201 return constraints;
202 }
203 }
204
211 {
212 // Add public inputs if needed
213 for (size_t idx = builder.num_public_inputs(); idx < NUM_PUBLIC_INPUTS; idx++) {
214 builder.add_public_variable(InnerBuilder::FF::random_element());
215 }
216 auto prover_instance = std::make_shared<InnerProverInstance>(builder);
217 auto verification_key = std::make_shared<InnerVerificationKey>(prover_instance->get_precomputed());
218
219 InnerProver prover(prover_instance, verification_key);
220 auto proof = prover.construct_proof();
221
222 WitnessVector witness_values;
223 RecursionConstraint recursion_constraint =
225 proof,
226 verification_key->to_field_elements(),
227 verification_key->hash(),
228 bb::fr::one(),
229 builder.num_public_inputs() - InnerIO::PUBLIC_INPUTS_SIZE,
231
232 return { recursion_constraint, witness_values };
233 }
234
240 {
241 // The outer circuit has IPA claims if using rollup IO and not the root rollup (which finalizes IPA)
242 return ProgramMetadata{ .has_ipa_claim = UseRollupIO && !IsRootRollup };
243 }
244
246 AcirConstraint honk_recursion_constraints,
247 WitnessVector witness_values,
248 const InvalidWitness::Target& invalid_witness_target)
249 {
250 switch (invalid_witness_target) {
252 break;
254 // Invalidate the circuit by modifying the vk hash
255 if constexpr (IS_SINGLE_RECURSIVE_VERIFICATION) {
256 witness_values[honk_recursion_constraints.key_hash] += fr::one();
257 } else {
258 witness_values[honk_recursion_constraints[0].key_hash] += fr::one();
259 }
260 break;
261 }
263 // Invalidate the circuit by modifying the first element of the vk (log circuit size)
264 std::vector<uint32_t> vk_indices;
265 if constexpr (IS_SINGLE_RECURSIVE_VERIFICATION) {
266 witness_values[honk_recursion_constraints.key[0]] += fr::one();
267 } else {
268 witness_values[honk_recursion_constraints[0].key[0]] += fr::one();
269 }
270 break;
271 }
273 // Invalidate the circuit by modifying the first element of the proof after the public inputs, which is a
274 // group element (vk commitment)
275 std::vector<uint32_t> proof_indices;
276 if constexpr (IS_SINGLE_RECURSIVE_VERIFICATION) {
277 proof_indices = honk_recursion_constraints.proof;
278 } else {
279 proof_indices = honk_recursion_constraints[0].proof;
280 }
281 size_t commitment_size = FrCodec::template calc_num_fields<typename InnerFlavor::Commitment>();
282 std::vector<bb::fr> mock_proof_element = FrCodec::serialize_to_fields(InnerFlavor::Commitment::one());
283 for (size_t idx = 0; idx < commitment_size; idx++) {
284 witness_values[proof_indices[InnerIO::PUBLIC_INPUTS_SIZE + idx]] = mock_proof_element[idx];
285 }
286 break;
287 }
288 }
289
290 return { honk_recursion_constraints, witness_values };
291 }
292
293 static void generate_constraints(AcirConstraint& honk_recursion_constraint, WitnessVector& witness_values)
294 {
296 std::vector<WitnessVector> witness_vectors;
297
298 for (size_t layer_idx = 0; layer_idx < N; layer_idx++) {
299 size_t current_layer_size = LayerSizes[layer_idx];
300 for (size_t idx = 0; idx < current_layer_size; idx++) {
301 if (layer_idx == 0) {
302 // If we are at the bottom layer, we create the circuit and then create the recursion constraint
303 // that verifies the circuit
304 auto [constraint, witnesses] = []() {
307 }();
308 constraints.emplace_back(std::move(constraint));
309 witness_vectors.emplace_back(std::move(witnesses));
310 } else {
311 // If we are in a layer above the bottom one, we take the recursion constraints at index idx and
312 // build a circuit the recursively verifies these recursion constraints
313 RecursionConstraint recursion_constraint = constraints[idx];
314 WitnessVector witnesses = witness_vectors[idx];
315
316 AcirFormat acir_format = constraint_to_acir_format(recursion_constraint);
317
318 AcirProgram acir_program{ .constraints = acir_format, .witness = witnesses };
319
320 std::tie(constraints[idx], witness_vectors[idx]) = [&]() {
321 auto builder = create_circuit<InnerBuilder>(acir_program, generate_metadata());
323 }();
324 }
325 }
326 }
327
328 // Merge the constraints by offsetting the relevant indices and concatenating the witness vectors
329 honk_recursion_constraint = merge_recursion_constraints(constraints, witness_vectors, witness_values);
330 }
331};
332
333template <typename Params>
335 : public ::testing::Test,
336 public TestClassWithPredicate<HonkRecursionConstraintTestingFunctions<typename Params::RecursiveFlavor,
337 Params::IsRootRollup,
338 Params::N,
339 Params::LayerSizes>> {
340 public:
341 using RecursiveFlavor = Params::RecursiveFlavor;
342 static constexpr bool IsRootRollup = Params::IsRootRollup;
343
344 protected:
346};
347
348// We test the predicate with vanilla recursion. This is enough as the predicate logic is a standalone component,
349// there's no inter-constraint interaction due to the existence or the value of the predicate.
351 testing::Types<HonkRecursionTestParams<UltraRecursiveFlavor_<UltraCircuitBuilder>, false, 1, { 1 }>,
355
357
359{
360 // The flavor with which we prove the outer circuit (the one verifying F_1, .., F_{s_1}) depends on what type of
361 // data the inner circuits have propagated and the builder.
364 typename TestFixture::RecursiveFlavor::NativeFlavor>;
365 TestFixture::template test_vk_independence<Flavor>();
366}
367
369{
371 TestFixture::test_constant_true(TestFixture::InvalidWitnessTarget::VKHash);
372}
373
375{
377 TestFixture::test_witness_true(TestFixture::InvalidWitnessTarget::VKHash);
378}
379
381{
383 TestFixture::test_witness_false_slow();
384}
385
387{
388 using RecursiveFlavor = TestFixture::RecursiveFlavor;
389 using Builder = TestFixture::Builder;
390 using InvalidWitnessTarget = TestFixture::InvalidWitnessTarget;
391
392 typename TestFixture::AcirConstraint constraint;
393 WitnessVector witness_values;
394 TestFixture::Base::generate_constraints(constraint, witness_values);
395
396 for (const auto predicate_mode : Predicate<InvalidWitnessTarget>::get_all()) {
397 Predicate<InvalidWitnessTarget> predicate{ .test_case = predicate_mode,
398 .invalid_witness = InvalidWitnessTarget::None };
399 auto [updated_constraint, updated_witness_values] =
400 TestFixture::update_witness_based_on_predicate(constraint, witness_values, predicate);
401
402 AcirFormat constraint_system = constraint_to_acir_format(updated_constraint);
403
404 AcirProgram program{ constraint_system, updated_witness_values };
405 ProgramMetadata metadata = TestFixture::Base::generate_metadata();
406 metadata.collect_gates_per_opcode = true;
407 auto builder = create_circuit<Builder>(program, metadata);
408
409 // Verify the gate count was recorded
410 EXPECT_EQ(program.constraints.gates_per_opcode.size(), 1);
411
412 // Get expected values based on predicate mode
413 auto [EXPECTED_GATE_COUNT, EXPECTED_ULTRA_OPS] = HONK_RECURSION_CONSTANTS<RecursiveFlavor>(predicate_mode);
414
415 // Assert gate count
416 EXPECT_EQ(program.constraints.gates_per_opcode[0], EXPECTED_GATE_COUNT);
417
418 // For MegaBuilder, also assert ultra ops count
419 if constexpr (IsMegaBuilder<Builder>) {
420 size_t actual_ultra_ops = builder.op_queue->get_current_subtable_size();
421 EXPECT_EQ(actual_ultra_ops, EXPECTED_ULTRA_OPS);
422 }
423 }
424}
425
426template <typename Params>
428 : public ::testing::Test,
429 public TestClass<HonkRecursionConstraintTestingFunctions<typename Params::RecursiveFlavor,
430 Params::IsRootRollup,
431 Params::N,
432 Params::LayerSizes>> {
433 public:
434 using RecursiveFlavor = Params::RecursiveFlavor;
435 static constexpr bool IsRootRollup = Params::IsRootRollup;
436
437 protected:
439};
440
444 HonkRecursionTestParams<UltraRecursiveFlavor_<UltraCircuitBuilder>, true, 1, { 2 }>, // Root rollup circuit
445 HonkRecursionTestParams<UltraZKRecursiveFlavor_<UltraCircuitBuilder>, false, 2, { 2, 1 }>, // Double recursion
446 HonkRecursionTestParams<UltraZKRecursiveFlavor_<UltraCircuitBuilder>, false, 2, { 2, 2 }>, // Merge two circuits
447 HonkRecursionTestParams<UltraRecursiveFlavor_<MegaCircuitBuilder>, false, 4, { 4, 3, 1, 1 }>>; // Complex flow
448
450
452{
453 if constexpr (TypeParam::IsRootRollup) {
454 TestFixture::template test_vk_independence<UltraZKFlavor>();
455 } else {
456 // The flavor with which we prove the outer circuit (the one verifying F_1, .., F_{s_1}) depends on what type of
457 // data the inner circuits have propagated and the builder.
460 typename TestFixture::RecursiveFlavor::NativeFlavor>;
461
462 TestFixture::template test_vk_independence<Flavor>();
463 }
464}
465
467{
469 [[maybe_unused]] std::vector<std::string> _ = TestFixture::test_tampering();
470}
471
473{
474 using Builder = TestFixture::Builder;
475
476 if constexpr (!TestFixture::IsRootRollup) {
477 GTEST_SKIP(); // We have already pinned the gate counts in this situation
478 }
479
480 typename TestFixture::AcirConstraint constraint;
481 WitnessVector witness_values;
482 TestFixture::Base::generate_constraints(constraint, witness_values);
483
484 AcirFormat constraint_system = constraint_to_acir_format(constraint);
485
486 AcirProgram program{ constraint_system, witness_values };
487 ProgramMetadata metadata = TestFixture::Base::generate_metadata();
488 metadata.collect_gates_per_opcode = true;
489 auto builder = create_circuit<Builder>(program, metadata);
490
491 // Verify the gate count was recorded
492 EXPECT_EQ(program.constraints.gates_per_opcode.size(), 2);
493
494 // Assert gate count
495 EXPECT_EQ(builder.get_num_finalized_gates_inefficient(), ROOT_ROLLUP_GATE_COUNT);
496}
#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)
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)
std::conditional_t< UseRollupIO, bb::stdlib::recursion::honk::RollupIO, bb::stdlib::recursion::honk::DefaultIO< InnerBuilder > > InnerIO
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.
Contains all the information required by a Honk prover to create a proof, constructed from a finalize...
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:52
testing::Types< HonkRecursionTestParams< UltraRecursiveFlavor_< UltraCircuitBuilder >, false, 1, { 1 }>, HonkRecursionTestParams< UltraZKRecursiveFlavor_< 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< UltraRecursiveFlavor_< 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)
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
TYPED_TEST_SUITE(CommitmentKeyTest, Curves)
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()