Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
honk_recursive_verifier.test.cpp
Go to the documentation of this file.
15
17
18// Test parameters: <RecursiveFlavor, IO>
19// IO determines the public inputs structure (DefaultIO or RollupIO) for both inner and outer circuits
20template <typename RecursiveFlavor_, typename IO_> struct RecursiveVerifierTestParams {
21 using RecursiveFlavor = RecursiveFlavor_;
22 using IO = IO_;
23};
24
25// Run the recursive verifier tests with conventional Ultra builder and Goblin builder
26// Note: UltraRecursiveFlavor_<UltraCircuitBuilder> + RollupIO covers the rollup case
27using TestConfigs = testing::Types<
37
46template <typename Params> class RecursiveVerifierTest : public testing::Test {
47
48 using RecursiveFlavor = typename Params::RecursiveFlavor;
49 using IO = typename Params::IO;
50
51 // Define types for the inner circuit, i.e. the circuit whose proof will be recursively verified
52 using InnerFlavor = typename RecursiveFlavor::NativeFlavor;
54 using InnerBuilder = typename InnerFlavor::CircuitBuilder;
56 using InnerCommitment = InnerFlavor::Commitment;
57 using InnerFF = InnerFlavor::FF;
59
60 // IO types: InnerIO uses InnerBuilder, OuterIO uses OuterBuilder
64
65 // Defines types for the outer circuit, i.e. the circuit of the recursive verifier
66 using OuterBuilder = typename RecursiveFlavor::CircuitBuilder;
72 using OuterIO = IO;
73
74 // RecursiveVerifier uses IO that matches the test's IO type
77
87 static InnerBuilder create_inner_circuit(size_t log_num_gates = 10)
88 {
90
91 // Create 2^log_n many add gates based on input log num gates
92 const size_t num_gates = (1 << log_num_gates);
93 for (size_t i = 0; i < num_gates; ++i) {
95 uint32_t a_idx = builder.add_variable(a);
96
99 fr d = a + b + c;
100 uint32_t b_idx = builder.add_variable(b);
101 uint32_t c_idx = builder.add_variable(c);
102 uint32_t d_idx = builder.add_variable(d);
103
104 builder.create_big_add_gate({ a_idx, b_idx, c_idx, d_idx, fr(1), fr(1), fr(1), fr(-1), fr(0) });
105 }
106
107 InnerIO::add_default(builder);
108
109 return builder;
110 }
111
112 public:
114
119 static void test_inner_circuit()
120 {
121 auto inner_circuit = create_inner_circuit();
122
123 bool result = CircuitChecker::check(inner_circuit);
124
125 EXPECT_EQ(result, true);
126 }
127
134 {
135 // Create an arbitrary inner circuit
136 auto inner_circuit = create_inner_circuit();
137 OuterBuilder outer_circuit;
138
139 // Compute native verification key
140 auto prover_instance = std::make_shared<InnerProverInstance>(inner_circuit);
141 auto honk_vk = std::make_shared<typename InnerFlavor::VerificationKey>(prover_instance->get_precomputed());
142 auto stdlib_vk_and_hash = std::make_shared<typename RecursiveFlavor::VKAndHash>(outer_circuit, honk_vk);
143 // Instantiate the recursive verifier using the native verification key
144 RecursiveVerifier verifier{ stdlib_vk_and_hash };
145
146 // Spot check some values in the recursive VK to ensure it was constructed correctly
147 EXPECT_EQ(
148 static_cast<uint64_t>(verifier.get_verifier_instance()->vk_and_hash->vk->log_circuit_size.get_value()),
149 honk_vk->log_circuit_size);
150 EXPECT_EQ(
151 static_cast<uint64_t>(verifier.get_verifier_instance()->vk_and_hash->vk->num_public_inputs.get_value()),
152 honk_vk->num_public_inputs);
153 for (auto [vk_poly, native_vk_poly] :
154 zip_view(verifier.get_verifier_instance()->vk_and_hash->vk->get_all(), honk_vk->get_all())) {
155 EXPECT_EQ(vk_poly.get_value(), native_vk_poly);
156 }
157 }
158
166 {
167 // Retrieves the trace blocks (each consisting of a specific gate) from the recursive verifier circuit
168 auto get_blocks = [](size_t inner_size) -> std::tuple<typename OuterBuilder::ExecutionTrace,
170 // Create an arbitrary inner circuit
171 auto inner_circuit = create_inner_circuit(inner_size);
172
173 // Generate a proof over the inner circuit
174 auto inner_prover_instance = std::make_shared<InnerProverInstance>(inner_circuit);
175 auto verification_key =
176 std::make_shared<typename InnerFlavor::VerificationKey>(inner_prover_instance->get_precomputed());
177 InnerProver inner_prover(inner_prover_instance, verification_key);
178 info("test circuit size: ", inner_prover_instance->dyadic_size());
179 auto inner_proof = inner_prover.construct_proof();
180
181 // Create a recursive verification circuit for the proof of the inner circuit
182 OuterBuilder outer_circuit;
183 auto stdlib_vk_and_hash =
184 std::make_shared<typename RecursiveFlavor::VKAndHash>(outer_circuit, verification_key);
185 RecursiveVerifier verifier{ stdlib_vk_and_hash };
186
187 // Convert native proof to stdlib and verify (verifier handles IPA splitting internally)
188 OuterStdlibProof stdlib_inner_proof(outer_circuit, inner_proof);
189 typename RecursiveVerifier::Output verifier_output = verifier.verify_proof(stdlib_inner_proof);
190
191 // IO of outer_circuit
193 inputs.pairing_inputs = verifier_output.points_accumulator;
194 if constexpr (IO::HasIPA) {
195 // Add ipa claim
196 inputs.ipa_claim = verifier_output.ipa_claim;
197
198 // Store ipa_proof
199 outer_circuit.ipa_proof = verifier_output.ipa_proof.get_value();
200 };
201 inputs.set_public();
202
203 auto outer_prover_instance = std::make_shared<OuterProverInstance>(outer_circuit);
204 auto outer_verification_key =
205 std::make_shared<typename OuterFlavor::VerificationKey>(outer_prover_instance->get_precomputed());
206
207 return { outer_circuit.blocks, outer_verification_key };
208 };
209
210 auto [blocks_10, verification_key_10] = get_blocks(10);
211 auto [blocks_14, verification_key_14] = get_blocks(14);
212
213 compare_ultra_blocks_and_verification_keys<OuterFlavor>({ blocks_10, blocks_14 },
214 { verification_key_10, verification_key_14 });
215 }
216
222 {
223 // Create an arbitrary inner circuit
224 auto inner_circuit = create_inner_circuit();
225
226 // Generate a proof over the inner circuit
227 auto prover_instance = std::make_shared<InnerProverInstance>(inner_circuit);
228 auto verification_key =
229 std::make_shared<typename InnerFlavor::VerificationKey>(prover_instance->get_precomputed());
230 InnerProver inner_prover(prover_instance, verification_key);
231 auto inner_proof = inner_prover.construct_proof();
232
233 // Create a recursive verification circuit for the proof of the inner circuit
234 OuterBuilder outer_circuit;
235 auto stdlib_vk_and_hash =
236 std::make_shared<typename RecursiveFlavor::VKAndHash>(outer_circuit, verification_key);
237 auto recursive_transcript = std::make_shared<typename RecursiveFlavor::Transcript>();
238 recursive_transcript->enable_manifest();
239 RecursiveVerifier verifier{ stdlib_vk_and_hash, recursive_transcript };
240
241 OuterStdlibProof stdlib_inner_proof(outer_circuit, inner_proof);
242 VerifierOutput output = verifier.verify_proof(stdlib_inner_proof);
243
244 // IO of outer_circuit
246 inputs.pairing_inputs = output.points_accumulator;
247 if constexpr (IO::HasIPA) {
248 // Add ipa claim
249 inputs.ipa_claim = output.ipa_claim;
250
251 // Store ipa_proof
252 outer_circuit.ipa_proof = output.ipa_proof.get_value();
253 };
254 inputs.set_public();
255
256 // Check for a failure flag in the recursive verifier circuit
257 EXPECT_EQ(outer_circuit.failed(), false) << outer_circuit.err();
258
259 // Check 1: Perform native verification then perform the pairing on the outputs of the recursive
260 // verifier and check that the result agrees.
261 auto vk_and_hash = std::make_shared<typename InnerFlavor::VKAndHash>(verification_key);
263 native_transcript->enable_manifest();
264 InnerVerifier native_verifier(vk_and_hash, native_transcript);
265 // inner_proof already contains combined honk + IPA for rollup flavors
266 bool native_result = native_verifier.verify_proof(inner_proof).result;
267
268 bool result = output.points_accumulator.check();
269 info("input pairing points result: ", result);
270 EXPECT_EQ(result, native_result);
271
272 // Check 2: Ensure that the underlying native and recursive verification algorithms agree by ensuring
273 // the manifests produced by each agree.
274 auto recursive_manifest = verifier.get_transcript()->get_manifest();
275 auto native_manifest = native_verifier.get_transcript()->get_manifest();
276 for (size_t i = 0; i < recursive_manifest.size(); ++i) {
277 EXPECT_EQ(recursive_manifest[i], native_manifest[i]);
278 }
279
280 // Check 3: Construct and verify a proof of the recursive verifier circuit
281 {
282 auto prover_instance = std::make_shared<OuterProverInstance>(outer_circuit);
283 auto verification_key =
284 std::make_shared<typename OuterFlavor::VerificationKey>(prover_instance->get_precomputed());
285 info("Recursive Verifier: num gates = ", outer_circuit.get_num_finalized_gates());
286 OuterProver prover(prover_instance, verification_key);
287 // construct_proof() already returns combined proof (honk + IPA) for rollup flavors
288 auto proof = prover.construct_proof();
289 auto outer_vk_and_hash = std::make_shared<typename OuterFlavor::VKAndHash>(verification_key);
290 OuterVerifier verifier(outer_vk_and_hash);
291 bool result = verifier.verify_proof(proof).result;
292 ASSERT_TRUE(result);
293 }
294 // Check the size of the recursive verifier
296 const auto expected_gate_count = std::get<0>(acir_format::HONK_RECURSION_CONSTANTS<RecursiveFlavor>());
297 ASSERT_EQ(outer_circuit.get_num_finalized_gates(), expected_gate_count)
298 << "MegaZKHonk Recursive verifier changed in Ultra gate count! Update this value if you "
299 "are sure this is expected.";
300 }
301 }
302
303 enum class TamperType {
304 MODIFY_SUMCHECK_UNIVARIATE, // Tests sumcheck round consistency constraint (circuit FAIL)
305 MODIFY_SUMCHECK_EVAL, // Tests final relation check constraint (circuit FAIL)
306 MODIFY_KZG_WITNESS, // Tests pairing check (circuit PASS, pairing FAIL)
307 MODIFY_LIBRA_EVAL, // Tests Libra consistency constraint (circuit FAIL, ZK only)
308 END
309 };
310
311 static void tamper_honk_proof(InnerProver& inner_prover,
312 typename InnerFlavor::Transcript::Proof& inner_proof,
314 {
315 using FF = InnerFF;
316 static constexpr size_t FIRST_WITNESS_INDEX = InnerFlavor::NUM_PRECOMPUTED_ENTITIES;
317
318 StructuredProof<InnerFlavor> structured_proof;
319 const auto num_public_inputs = inner_prover.num_public_inputs();
320 const size_t log_n = InnerFlavor::USE_PADDING ? InnerFlavor::VIRTUAL_LOG_N : inner_prover.log_dyadic_size();
321 structured_proof.deserialize(inner_prover.get_transcript()->test_get_proof_data(), num_public_inputs, log_n);
322
323 switch (type) {
325 FF delta = FF::random_element();
326 structured_proof.sumcheck_univariates[0].value_at(0) += delta;
327 structured_proof.sumcheck_univariates[0].value_at(1) -= delta;
328 break;
329 }
331 structured_proof.sumcheck_evaluations[FIRST_WITNESS_INDEX] = FF::random_element();
332 break;
334 structured_proof.kzg_w_comm = structured_proof.kzg_w_comm * FF::random_element();
335 break;
337 if constexpr (InnerFlavor::HasZK) {
338 structured_proof.libra_quotient_eval = FF::random_element();
339 }
340 break;
341 case TamperType::END:
342 break;
343 }
344
345 structured_proof.serialize(inner_prover.get_transcript()->test_get_proof_data(), log_n);
346 inner_prover.get_transcript()->test_set_proof_parsing_state(
348 inner_proof = inner_prover.export_proof();
349 }
350
352 {
353 for (size_t idx = 0; idx < static_cast<size_t>(TamperType::END); idx++) {
354 TamperType tamper_type = static_cast<TamperType>(idx);
355
356 if (tamper_type == TamperType::MODIFY_LIBRA_EVAL && !InnerFlavor::HasZK) {
357 continue;
358 }
359
360 // Create an arbitrary inner circuit
361 auto inner_circuit = create_inner_circuit();
362
363 // Generate a proof over the inner circuit
364 auto prover_instance = std::make_shared<InnerProverInstance>(inner_circuit);
365 auto inner_verification_key =
366 std::make_shared<typename InnerFlavor::VerificationKey>(prover_instance->get_precomputed());
367 InnerProver inner_prover(prover_instance, inner_verification_key);
368 auto inner_proof = inner_prover.construct_proof();
369
370 // Tamper with the proof to be verified
371 tamper_honk_proof(inner_prover, inner_proof, tamper_type);
372
373 // Create a recursive verification circuit for the tampered proof
374 OuterBuilder outer_circuit;
375 auto stdlib_vk_and_hash =
376 std::make_shared<typename RecursiveFlavor::VKAndHash>(outer_circuit, inner_verification_key);
377 RecursiveVerifier verifier{ stdlib_vk_and_hash };
378 OuterStdlibProof stdlib_inner_proof(outer_circuit, inner_proof);
379 VerifierOutput output = verifier.verify_proof(stdlib_inner_proof);
380
381 if (tamper_type == TamperType::MODIFY_KZG_WITNESS) {
382 // Expected to result in pairing failure but no circuit constraint violations
383 EXPECT_TRUE(CircuitChecker::check(outer_circuit));
384 EXPECT_FALSE(output.points_accumulator.check());
385 } else {
386 // All other tamper types should cause a circuit constraint violation
387 EXPECT_FALSE(CircuitChecker::check(outer_circuit));
388 }
389 }
390 }
391
402 {
403 // Create an arbitrary inner circuit
404 auto inner_circuit = create_inner_circuit();
405
406 // Generate a proof over the inner circuit
407 auto prover_instance = std::make_shared<InnerProverInstance>(inner_circuit);
408 auto verification_key =
409 std::make_shared<typename InnerFlavor::VerificationKey>(prover_instance->get_precomputed());
410 InnerProver inner_prover(prover_instance, verification_key);
411 auto inner_proof = inner_prover.construct_proof();
412
413 // Create a recursive verification circuit for the proof of the inner circuit
414 OuterBuilder outer_circuit;
415 auto stdlib_vk_and_hash =
416 std::make_shared<typename RecursiveFlavor::VKAndHash>(outer_circuit, verification_key);
417 RecursiveVerifier verifier{ stdlib_vk_and_hash };
418
419 // Fix witness for VK fields to ensure they're properly constrained
420 verifier.get_verifier_instance()->vk_and_hash->vk->num_public_inputs.fix_witness();
421 verifier.get_verifier_instance()->vk_and_hash->vk->pub_inputs_offset.fix_witness();
422 verifier.get_verifier_instance()->vk_and_hash->vk->log_circuit_size.fix_witness();
423
424 OuterStdlibProof stdlib_inner_proof(outer_circuit, inner_proof);
425 VerifierOutput output = verifier.verify_proof(stdlib_inner_proof);
426 auto pairing_points = output.points_accumulator;
427
428 // The pairing points are public outputs from the recursive verifier that will be verified externally via a
429 // pairing check. While they are computed within the circuit (via batch_mul for P0 and negation for P1), their
430 // output coordinates may not appear in multiple constraint gates. Calling fix_witness() adds explicit
431 // constraints on these values. Without these constraints, the StaticAnalyzer detects unconstrained variables
432 // (coordinate limbs) that appear in only one gate. This ensures the pairing point coordinates are properly
433 // constrained within the circuit itself, rather than relying solely on them being public outputs.
434 pairing_points.fix_witness();
435
436 // For RollupIO: Fix the IPA claim's bigfield elements (challenge and evaluation).
437 // When reconstructed from public inputs, bigfield::construct_from_limbs creates a prime_basis_limb
438 // that's computed as a linear combination of the binary limbs. Since the IPA claim is just propagated, this
439 // prime_basis_limb appears in only one gate.
440 if constexpr (IO::HasIPA) {
441 output.ipa_claim.opening_pair.challenge.fix_witness();
442 output.ipa_claim.opening_pair.evaluation.fix_witness();
443 }
444
445 info("Recursive Verifier: num gates = ", outer_circuit.get_num_finalized_gates_inefficient());
446
447 // Check for a failure flag in the recursive verifier circuit
448 EXPECT_EQ(outer_circuit.failed(), false) << outer_circuit.err();
449
450 outer_circuit.finalize_circuit();
451
452 // Run static analysis to detect unconstrained variables
453 // Use the appropriate analyzer based on the outer builder type
454 using Analyzer =
456 auto graph = Analyzer(outer_circuit);
457 auto [cc, variables_in_one_gate] = graph.analyze_circuit(/*filter_cc=*/true);
458
459 // We expect exactly one connected component (all variables properly connected)
460 EXPECT_EQ(cc.size(), 1);
461
462 // Expected variables in one gate:
463 size_t expected_unconstrained = 0;
464 EXPECT_EQ(variables_in_one_gate.size(), expected_unconstrained);
465 }
466};
467
469
471{
472 TestFixture::test_inner_circuit();
473}
474
475HEAVY_TYPED_TEST(RecursiveVerifierTest, RecursiveVerificationKey)
476{
477 TestFixture::test_recursive_verification_key_creation();
478}
479
480HEAVY_TYPED_TEST(RecursiveVerifierTest, SingleRecursiveVerification)
481{
482 TestFixture::test_recursive_verification();
483};
484
486{
487 using RecursiveFlavor = typename TypeParam::RecursiveFlavor;
488 if constexpr (IsAnyOf<RecursiveFlavor,
492 TestFixture::test_independent_vk_hash();
493 } else {
494 GTEST_SKIP() << "Not built for this parameter";
495 }
496};
497
498HEAVY_TYPED_TEST(RecursiveVerifierTest, SingleRecursiveVerificationFailure)
499{
500 TestFixture::test_recursive_verification_fails();
501};
502
509HEAVY_TYPED_TEST(RecursiveVerifierTest, GraphAnalysisOfRecursiveVerifier)
510{
511 TestFixture::test_recursive_verification_with_graph_analysis();
512};
513
514#ifdef DISABLE_HEAVY_TESTS
515// Null test
516TEST(RecursiveVerifierTest, DoNothingTestToEnsureATestExists) {}
517#endif
518} // namespace bb::stdlib::recursion::honk
The recursive counterpart to MegaZKFlavor.
Contains all the information required by a Honk prover to create a proof, constructed from a finalize...
static bool check(const Builder &circuit)
Check the witness satisifies the circuit.
size_t num_public_inputs() const
const std::shared_ptr< Transcript > & get_transcript() const
size_t log_dyadic_size() const
Proof export_proof()
Export the complete proof, including IPA proof for rollup circuits.
The recursive counterpart to the "native" Ultra flavor.
const std::shared_ptr< Instance > & get_verifier_instance() const
Get the verifier instance (for accessing VK and witness commitments in Chonk/Goblin)
typename Flavor::VerificationKey VerificationKey
std::conditional_t< IsRecursive, stdlib::recursion::honk::UltraRecursiveVerifierOutput< Builder >, UltraVerifierOutput< Flavor > > Output
const std::shared_ptr< Transcript > & get_transcript() const
Get the transcript (for accessing manifest in tests)
Output verify_proof(const Proof &proof)
Perform ultra verification.
The recursive counterpart to UltraZKFlavor.
A simple wrapper around a vector of stdlib field elements representing a proof.
Definition proof.hpp:20
Manages the data that is propagated on the public inputs of an application/function circuit.
Test suite for recursive verification of Honk proofs for both Ultra and Mega arithmetisation.
static void test_recursive_verification()
Construct a recursive verification circuit for the proof of an inner circuit then call check_circuit ...
static InnerBuilder create_inner_circuit(size_t log_num_gates=10)
Create a non-trivial arbitrary inner circuit, the proof of which will be recursively verified.
std::conditional_t< IO::HasIPA, bb::RollupIO, bb::DefaultIO > NativeIO
static void tamper_honk_proof(InnerProver &inner_prover, typename InnerFlavor::Transcript::Proof &inner_proof, TamperType type)
static void test_inner_circuit()
Create inner circuit and call check_circuit on it.
static void test_recursive_verification_key_creation()
Instantiate a recursive verification key from the native verification key produced by the inner cicui...
static void test_independent_vk_hash()
Ensures that the recursive verifier circuit for two inner circuits of different size is the same as t...
std::conditional_t< IO::HasIPA, RollupIO, DefaultIO< InnerBuilder > > InnerIO
std::conditional_t< IsMegaBuilder< OuterBuilder >, MegaFlavor, UltraFlavor > OuterFlavor
static void test_recursive_verification_with_graph_analysis()
Test recursive verification with static graph analysis to detect unconstrained variables.
typename RecursiveVerifier::VerificationKey VerificationKey
The data that is propagated on the public inputs of a rollup circuit.
#define info(...)
Definition log.hpp:93
AluTraceBuilder builder
Definition alu.test.cpp:124
FF a
FF b
Base class templates shared across Honk flavors.
AvmProvingInputs inputs
AvmFlavorSettings::FF FF
Definition field.hpp:10
std::filesystem::path bb_crs_path()
void init_file_crs_factory(const std::filesystem::path &path)
testing::Types< RecursiveVerifierTestParams< MegaRecursiveFlavor_< MegaCircuitBuilder >, DefaultIO< MegaCircuitBuilder > >, RecursiveVerifierTestParams< MegaRecursiveFlavor_< UltraCircuitBuilder >, DefaultIO< UltraCircuitBuilder > >, RecursiveVerifierTestParams< UltraRecursiveFlavor_< UltraCircuitBuilder >, DefaultIO< UltraCircuitBuilder > >, RecursiveVerifierTestParams< UltraRecursiveFlavor_< UltraCircuitBuilder >, RollupIO >, RecursiveVerifierTestParams< UltraRecursiveFlavor_< MegaCircuitBuilder >, DefaultIO< MegaCircuitBuilder > >, RecursiveVerifierTestParams< UltraZKRecursiveFlavor_< UltraCircuitBuilder >, DefaultIO< UltraCircuitBuilder > >, RecursiveVerifierTestParams< UltraZKRecursiveFlavor_< MegaCircuitBuilder >, DefaultIO< MegaCircuitBuilder > >, RecursiveVerifierTestParams< MegaZKRecursiveFlavor_< MegaCircuitBuilder >, DefaultIO< MegaCircuitBuilder > >, RecursiveVerifierTestParams< MegaZKRecursiveFlavor_< UltraCircuitBuilder >, DefaultIO< UltraCircuitBuilder > > > TestConfigs
TYPED_TEST_SUITE(RecursiveVerifierTest, TestConfigs)
field< Bn254FrParams > fr
Definition fr.hpp:155
TEST(BoomerangMegaCircuitBuilder, BasicCircuit)
StaticAnalyzer_< bb::fr, bb::MegaCircuitBuilder > MegaStaticAnalyzer
Definition graph.hpp:189
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
Full Honk proof layout (used by UltraVerifier).
Test utility for deserializing/serializing proof data into typed structures.
static field random_element(numeric::RNG *engine=nullptr) noexcept
An object storing two EC points that represent the inputs to a pairing check.
Output type for recursive ultra verification.
#define HEAVY_TYPED_TEST(x, y)
Definition test.hpp:11
VectorField result