@@ -39,21 +39,25 @@ class AvmRecursiveTests : public ::testing::Test {
3939 // by reference.
4040 static void create_and_verify_native_proof (NativeProofResult& proof_result)
4141 {
42- auto [trace, public_inputs] = testing::get_minimal_trace_with_pi ();
42+ static auto [cached_verified, cached_proof_result] = []() {
43+ auto [trace, public_inputs] = testing::get_minimal_trace_with_pi ();
4344
44- const auto public_inputs_cols = public_inputs.to_columns ();
45+ const auto public_inputs_cols = public_inputs.to_columns ();
4546
46- InnerProver prover;
47- const auto [proof, vk_data] = prover.prove (std::move (trace));
48- const auto verification_key = InnerProver::create_verification_key (vk_data);
49- InnerVerifier verifier (verification_key);
47+ InnerProver prover;
48+ const auto [proof, vk_data] = prover.prove (std::move (trace));
49+ const auto verification_key = InnerProver::create_verification_key (vk_data);
50+ InnerVerifier verifier (verification_key);
5051
51- const bool verified = verifier.verify_proof (proof, public_inputs_cols);
52+ const bool verified = verifier.verify_proof (proof, public_inputs_cols);
5253
53- // Should be in principle ASSERT_TRUE, but compiler does not like it.
54- EXPECT_TRUE (verified) << " native proof verification failed" ;
54+ return std::pair<bool , NativeProofResult>{
55+ verified, NativeProofResult{ proof, verification_key, public_inputs_cols }
56+ };
57+ }();
5558
56- proof_result = { proof, verification_key, public_inputs_cols };
59+ ASSERT_TRUE (cached_verified) << " native proof verification failed" ;
60+ proof_result = cached_proof_result;
5761 }
5862};
5963
@@ -154,4 +158,158 @@ TEST_F(AvmRecursiveTests, GoblinRecursion)
154158 EXPECT_TRUE (result);
155159}
156160
161+ // Similar to GoblinRecursion, but with PI validation disabled and garbage PIs in the public inputs.
162+ // This is important as long as we use a fallback mechanism for the AVM proofs.
163+ TEST_F (AvmRecursiveTests, GoblinRecursionWithoutPIValidation)
164+ {
165+ // Type aliases specific to GoblinRecursion test
166+ using AvmRecursiveVerifier = AvmGoblinRecursiveVerifier;
167+ using OuterBuilder = typename UltraRollupFlavor::CircuitBuilder;
168+ using UltraFF = UltraRecursiveFlavor_<OuterBuilder>::FF;
169+ using UltraRollupProver = UltraProver_<UltraRollupFlavor>;
170+ using NativeVerifierCommitmentKey = typename AvmFlavor::VerifierCommitmentKey;
171+
172+ NativeProofResult proof_result;
173+ std::cout << " Creating and verifying native proof..." << std::endl;
174+ std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now ();
175+ ASSERT_NO_FATAL_FAILURE ({ create_and_verify_native_proof (proof_result); });
176+ std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now ();
177+ std::cout << " Time taken (native proof): " << std::chrono::duration_cast<std::chrono::seconds>(end - start).count ()
178+ << " s" << std::endl;
179+
180+ auto [proof, verification_key, public_inputs_cols] = proof_result;
181+ // Set fallback / disable PI validation
182+ proof.insert (proof.begin (),
183+ 1 ); // TODO(#14234)[Unconditional PIs validation]: PI validation is disabled for this test.
184+
185+ // Construct stdlib representations of the proof, public inputs and verification key
186+ OuterBuilder outer_circuit;
187+ stdlib::Proof<OuterBuilder> stdlib_proof (outer_circuit, proof);
188+
189+ std::vector<std::vector<UltraFF>> public_inputs_ct;
190+ public_inputs_ct.reserve (public_inputs_cols.size ());
191+ // Use GARBAGE in public inputs and confirm that PI validation is disabled!
192+ for (const auto & vec : public_inputs_cols) {
193+ std::vector<UltraFF> vec_ct;
194+ vec_ct.reserve (vec.size ());
195+ for (const auto & _ : vec) {
196+ vec_ct.push_back (UltraFF::from_witness (&outer_circuit, FF::random_element ()));
197+ }
198+ public_inputs_ct.push_back (vec_ct);
199+ }
200+
201+ auto key_fields_native = verification_key->to_field_elements ();
202+ std::vector<UltraFF> outer_key_fields;
203+ for (const auto & f : key_fields_native) {
204+ UltraFF val = UltraFF::from_witness (&outer_circuit, f);
205+ outer_key_fields.push_back (val);
206+ }
207+
208+ // Construct the AVM recursive verifier and verify the proof
209+ // Scoped to free memory of AvmRecursiveVerifier.
210+ auto verifier_output = [&]() {
211+ std::cout << " Constructing AvmRecursiveVerifier and verifying proof..." << std::endl;
212+ std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now ();
213+ AvmRecursiveVerifier avm_rec_verifier (outer_circuit, outer_key_fields);
214+ auto result = avm_rec_verifier.verify_proof (stdlib_proof, public_inputs_ct);
215+ std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now ();
216+ std::cout << " Time taken (recursive verification): "
217+ << std::chrono::duration_cast<std::chrono::seconds>(end - start).count () << " s" << std::endl;
218+ return result;
219+ }();
220+
221+ verifier_output.points_accumulator .set_public ();
222+ verifier_output.ipa_claim .set_public ();
223+ outer_circuit.ipa_proof = verifier_output.ipa_proof .get_value ();
224+
225+ // Ensure that the pairing check is satisfied on the outputs of the recursive verifier
226+ NativeVerifierCommitmentKey pcs_vkey{};
227+ bool agg_output_valid = pcs_vkey.pairing_check (verifier_output.points_accumulator .P0 .get_value (),
228+ verifier_output.points_accumulator .P1 .get_value ());
229+ ASSERT_TRUE (agg_output_valid) << " Pairing points (aggregation state) are not valid." ;
230+ ASSERT_FALSE (outer_circuit.failed ()) << " Outer circuit has failed." ;
231+
232+ vinfo (" Recursive verifier: finalized num gates = " , outer_circuit.num_gates );
233+
234+ // Construct and verify an Ultra Rollup proof of the AVM recursive verifier circuit. This proof carries an IPA claim
235+ // from ECCVM recursive verification in its public inputs that will be verified as part of the UltraRollupVerifier.
236+ auto outer_proving_key = std::make_shared<DeciderProvingKey_<UltraRollupFlavor>>(outer_circuit);
237+
238+ // Scoped to free memory of UltraRollupProver.
239+ auto outer_proof = [&]() {
240+ auto verification_key =
241+ std::make_shared<UltraRollupFlavor::VerificationKey>(outer_proving_key->get_precomputed ());
242+ UltraRollupProver outer_prover (outer_proving_key, verification_key);
243+ return outer_prover.construct_proof ();
244+ }();
245+
246+ // Verify the proof of the Ultra circuit that verified the AVM recursive verifier circuit
247+ auto outer_verification_key =
248+ std::make_shared<UltraRollupFlavor::VerificationKey>(outer_proving_key->get_precomputed ());
249+ VerifierCommitmentKey<curve::Grumpkin> ipa_verification_key (1 << CONST_ECCVM_LOG_N);
250+ UltraRollupVerifier final_verifier (outer_verification_key, ipa_verification_key);
251+
252+ bool result = final_verifier.template verify_proof <bb::RollupIO>(outer_proof, outer_proving_key->ipa_proof ).result ;
253+ EXPECT_TRUE (result);
254+ }
255+
256+ // Ensures that the recursive verifier fails with wrong PIs.
257+ TEST_F (AvmRecursiveTests, GoblinRecursionFailsWithWrongPIs)
258+ {
259+ // Type aliases specific to GoblinRecursion test
260+ using AvmRecursiveVerifier = AvmGoblinRecursiveVerifier;
261+ using OuterBuilder = typename UltraRollupFlavor::CircuitBuilder;
262+ using UltraFF = UltraRecursiveFlavor_<OuterBuilder>::FF;
263+
264+ NativeProofResult proof_result;
265+ std::cout << " Creating and verifying native proof..." << std::endl;
266+ std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now ();
267+ ASSERT_NO_FATAL_FAILURE ({ create_and_verify_native_proof (proof_result); });
268+ std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now ();
269+ std::cout << " Time taken (native proof): " << std::chrono::duration_cast<std::chrono::seconds>(end - start).count ()
270+ << " s" << std::endl;
271+
272+ auto [proof, verification_key, public_inputs_cols] = proof_result;
273+ // PI validation is enabled.
274+ proof.insert (proof.begin (), 0 ); // TODO(#14234)[Unconditional PIs validation]: remove this
275+
276+ // Construct stdlib representations of the proof, public inputs and verification key
277+ OuterBuilder outer_circuit;
278+ stdlib::Proof<OuterBuilder> stdlib_proof (outer_circuit, proof);
279+
280+ std::vector<std::vector<UltraFF>> public_inputs_ct;
281+ public_inputs_ct.reserve (public_inputs_cols.size ());
282+ for (const auto & vec : public_inputs_cols) {
283+ std::vector<UltraFF> vec_ct;
284+ vec_ct.reserve (vec.size ());
285+ for (const auto & val : vec) {
286+ vec_ct.push_back (UltraFF::from_witness (&outer_circuit, val));
287+ }
288+ public_inputs_ct.push_back (vec_ct);
289+ }
290+ // Mutate some PI entry so that we can confirm that PI validation is enabled and fails!
291+ public_inputs_ct[1 ][5 ] += 1 ;
292+
293+ auto key_fields_native = verification_key->to_field_elements ();
294+ std::vector<UltraFF> outer_key_fields;
295+ for (const auto & f : key_fields_native) {
296+ UltraFF val = UltraFF::from_witness (&outer_circuit, f);
297+ outer_key_fields.push_back (val);
298+ }
299+
300+ // Construct the AVM recursive verifier and verify the proof
301+ // Scoped to free memory of AvmRecursiveVerifier.
302+ {
303+ std::cout << " Constructing AvmRecursiveVerifier and verifying proof..." << std::endl;
304+ std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now ();
305+ AvmRecursiveVerifier avm_rec_verifier (outer_circuit, outer_key_fields);
306+ auto result = avm_rec_verifier.verify_proof (stdlib_proof, public_inputs_ct);
307+ std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now ();
308+ std::cout << " Time taken (recursive verification): "
309+ << std::chrono::duration_cast<std::chrono::seconds>(end - start).count () << " s" << std::endl;
310+ };
311+
312+ ASSERT_TRUE (outer_circuit.failed ()) << " Outer circuit SHOULD fail with bad PIs." ;
313+ }
314+
157315} // namespace bb::avm2::constraining
0 commit comments