Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
recursive_verifier.test.cpp
Go to the documentation of this file.
15
16#include <gtest/gtest.h>
17
18namespace bb::avm2::constraining {
19
20class AvmRecursiveTests : public ::testing::Test {
21 public:
26
28
33
34 // Helper function to create and verify native proof. Due to the way ASSERT_TRUE
35 // works, this routine needs to return void and therefore we feed proof_result
36 // by reference.
38 {
39 static auto [cached_verified, cached_proof_result] = []() {
40 auto [trace, public_inputs] = testing::get_minimal_trace_with_pi();
41
42 const auto public_inputs_cols = public_inputs.to_columns();
43
44 InnerProver prover;
45 const auto proof = prover.prove(std::move(trace));
46 InnerVerifier verifier;
47
48 const bool verified = verifier.verify_proof(proof, public_inputs_cols);
49
50 return std::pair<bool, NativeProofResult>{ verified, NativeProofResult{ proof, public_inputs_cols } };
51 }();
52
53 ASSERT_TRUE(cached_verified) << "native proof verification failed";
54 proof_result = cached_proof_result;
55 }
56};
57
58// Parameterized test class for testing with and without proof padding
59class AvmRecursiveTestsParameterized : public AvmRecursiveTests, public ::testing::WithParamInterface<bool> {};
60
71{
73 GTEST_SKIP() << "Skipping slow test";
74 }
75
76 const bool pad_proof = GetParam();
77
78 // Type aliases specific to TwoLayerAvmRecursion test
79 using OuterBuilder = typename UltraFlavor::CircuitBuilder;
81 using UltraRollupProver = UltraProver_<UltraFlavor>;
82
83 NativeProofResult proof_result;
84 std::cout << "Creating and verifying native proof..." << std::endl;
85 std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
86 ASSERT_NO_FATAL_FAILURE({ create_and_verify_native_proof(proof_result); });
87 std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
88 std::cout << "Time taken (native proof): " << std::chrono::duration_cast<std::chrono::seconds>(end - start).count()
89 << "s" << std::endl;
90
91 auto [proof, public_inputs_cols] = proof_result;
92
93 // Optionally pad the proof to match production behavior
94 if (pad_proof) {
95 std::cout << "Padding proof from " << proof.size() << " to " << AVM_V2_PROOF_LENGTH_IN_FIELDS_PADDED
96 << " fields" << std::endl;
97 ASSERT_LE(proof.size(), AVM_V2_PROOF_LENGTH_IN_FIELDS_PADDED) << "Proof exceeds padded length";
99 }
100
101 // Construct stdlib representations of the proof, public inputs and verification key
102 OuterBuilder outer_circuit;
103 stdlib::Proof<OuterBuilder> stdlib_proof(outer_circuit, proof);
104
105 std::vector<std::vector<UltraFF>> public_inputs_ct;
106 public_inputs_ct.reserve(public_inputs_cols.size());
107 for (const auto& vec : public_inputs_cols) {
109 vec_ct.reserve(vec.size());
110 for (const auto& val : vec) {
111 vec_ct.push_back(UltraFF::from_witness(&outer_circuit, val));
112 }
113 public_inputs_ct.push_back(vec_ct);
114 }
115
116 // Construct the AVM recursive verifier and verify the proof
117 // Scoped to free memory of AvmRecursiveVerifier.
118 auto verifier_output = [&]() {
119 std::cout << "Constructing AvmRecursiveVerifier and verifying " << (pad_proof ? "padded " : "") << "proof..."
120 << std::endl;
121 std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
122 TwoLayerAvmRecursiveVerifier avm_rec_verifier(outer_circuit);
123 auto result = avm_rec_verifier.verify_proof(stdlib_proof, public_inputs_ct);
124 std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
125 std::cout << "Time taken (recursive verification): "
126 << std::chrono::duration_cast<std::chrono::seconds>(end - start).count() << "s" << std::endl;
127 return result;
128 }();
129
131 inputs.pairing_inputs = verifier_output.points_accumulator;
132 inputs.ipa_claim = verifier_output.ipa_claim;
133 inputs.set_public();
134 outer_circuit.ipa_proof = verifier_output.ipa_proof.get_value();
135
136 // Ensure that the pairing check is satisfied on the outputs of the recursive verifier
137 ASSERT_TRUE(verifier_output.points_accumulator.check()) << "Pairing points (aggregation state) are not valid.";
138 ASSERT_FALSE(outer_circuit.failed()) << "Outer circuit has failed.";
139
140 vinfo("Recursive verifier",
141 (pad_proof ? " (padded proof)" : ""),
142 ": finalized num gates = ",
143 outer_circuit.num_gates());
144
145 // Construct and verify an Ultra Rollup proof of the AVM recursive verifier circuit. This proof carries an IPA claim
146 // from ECCVM recursive verification in its public inputs that will be verified as part of the UltraRollupVerifier.
147 auto outer_proving_key = std::make_shared<ProverInstance_<UltraFlavor>>(outer_circuit);
148
149 // Scoped to free memory of UltraRollupProver.
150 auto outer_proof = [&]() {
151 auto verification_key = std::make_shared<UltraFlavor::VerificationKey>(outer_proving_key->get_precomputed());
152 UltraRollupProver outer_prover(outer_proving_key, verification_key);
153 return outer_prover.construct_proof();
154 }();
155
156 // Verify the proof of the Ultra circuit that verified the AVM recursive verifier circuit
157 auto outer_verification_key = std::make_shared<UltraFlavor::VerificationKey>(outer_proving_key->get_precomputed());
158 auto outer_vk_and_hash = std::make_shared<UltraFlavor::VKAndHash>(outer_verification_key);
159 UltraRollupVerifier final_verifier(outer_vk_and_hash);
160
161 bool result = final_verifier.verify_proof(outer_proof).result;
162 EXPECT_TRUE(result);
163}
164
165// Test that the transcript operations performed during AVM recursive verification match the ones performed by the
166// function defined in the AvmRecursiveFlavor::Transcript class
168{
170 GTEST_SKIP() << "Skipping slow test";
171 }
172
174
175 const bool pad_proof = GetParam();
176
177 NativeProofResult proof_result;
178 std::cout << "Creating and verifying native proof..." << std::endl;
179 std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
180 ASSERT_NO_FATAL_FAILURE({ create_and_verify_native_proof(proof_result); });
181 std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
182 std::cout << "Time taken (native proof): " << std::chrono::duration_cast<std::chrono::seconds>(end - start).count()
183 << "s" << std::endl;
184
185 auto [proof, public_inputs_cols] = proof_result;
186
187 // Optionally pad the proof to match production behavior
188 if (pad_proof) {
189 std::cout << "Padding proof from " << proof.size() << " to " << AVM_V2_PROOF_LENGTH_IN_FIELDS_PADDED
190 << " fields" << std::endl;
191 ASSERT_LE(proof.size(), AVM_V2_PROOF_LENGTH_IN_FIELDS_PADDED) << "Proof exceeds padded length";
193 }
194
195 // Construct stdlib representations of the proof, public inputs and verification key
197 stdlib::Proof<MegaCircuitBuilder> stdlib_proof(builder, proof);
198
199 std::vector<std::vector<FF>> public_inputs_ct;
200 public_inputs_ct.reserve(public_inputs_cols.size());
201 for (const auto& vec : public_inputs_cols) {
202 std::vector<FF> vec_ct;
203 vec_ct.reserve(vec.size());
204 for (const auto& val : vec) {
205 vec_ct.push_back(FF::from_witness(&builder, val));
206 }
207 public_inputs_ct.push_back(vec_ct);
208 }
209
210 // Construct the AVM recursive verifier and verify the proof
211 // Scoped to free memory of AvmRecursiveVerifier.
212 FF final_state_full_verification;
214 transcript->enable_manifest();
215 {
216 std::cout << "Constructing AvmRecursiveVerifier and verifying proof..." << std::endl;
217 std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
218 AvmRecursiveVerifier avm_rec_verifier(builder, transcript);
219 [[maybe_unused]] auto _result = avm_rec_verifier.verify_proof(stdlib_proof, public_inputs_ct);
220 std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
221 std::cout << "Time taken (recursive verification): "
222 << std::chrono::duration_cast<std::chrono::seconds>(end - start).count() << "s" << std::endl;
223 final_state_full_verification = avm_rec_verifier.hash_avm_transcript(stdlib_proof);
224 };
225
226 // Perform only transcript operations
227 FF final_state_transcript_operations_only;
228 auto mocked_transcript = std::make_shared<AvmRecursiveFlavor::Transcript>();
229 {
230 std::tie(final_state_transcript_operations_only, mocked_transcript) =
232 }
233
234 // Check that the native values underlying the final states match
235 EXPECT_EQ(final_state_full_verification.get_value(), final_state_transcript_operations_only.get_value());
236
237 // Check consistency of the transcripts
238 auto manifest = transcript->get_manifest();
239 auto mocked_manifest = mocked_transcript->get_manifest();
240
241 BB_ASSERT_GT(manifest.size(), 0U);
242 BB_ASSERT_EQ(manifest.size(), mocked_manifest.size());
243 for (size_t round = 0; round < manifest.size(); ++round) {
244 ASSERT_EQ(manifest[round], mocked_manifest[round])
245 << std::format("Real/Mocked manifest discrepancy in round {}", round);
246 }
247
248 // Check that the circuit is satisfied
249 final_state_transcript_operations_only.assert_equal(final_state_full_verification);
250 EXPECT_TRUE(CircuitChecker::check(builder));
251 EXPECT_TRUE(!builder.failed());
252}
253
256 ::testing::Values(false, true),
257 [](const auto& info) { return info.param ? "Padded" : "Unpadded"; });
258
259// Ensures that the recursive verifier fails with wrong PIs.
260TEST_F(AvmRecursiveTests, TwoLayerAvmRecursionFailsWithWrongPIs)
261{
263 GTEST_SKIP() << "Skipping slow test";
264 }
265
266 // Type aliases specific to TwoLayerAvmRecursion test
267 using OuterBuilder = typename UltraFlavor::CircuitBuilder;
269
270 NativeProofResult proof_result;
271 std::cout << "Creating and verifying native proof..." << std::endl;
272 std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
273 ASSERT_NO_FATAL_FAILURE({ create_and_verify_native_proof(proof_result); });
274 std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
275 std::cout << "Time taken (native proof): " << std::chrono::duration_cast<std::chrono::seconds>(end - start).count()
276 << "s" << std::endl;
277
278 auto [proof, public_inputs_cols] = proof_result;
279
280 // Construct stdlib representations of the proof, public inputs and verification key
281 OuterBuilder outer_circuit;
282 stdlib::Proof<OuterBuilder> stdlib_proof(outer_circuit, proof);
283
284 std::vector<std::vector<UltraFF>> public_inputs_ct;
285 public_inputs_ct.reserve(public_inputs_cols.size());
286 for (const auto& vec : public_inputs_cols) {
288 vec_ct.reserve(vec.size());
289 for (const auto& val : vec) {
290 vec_ct.push_back(UltraFF::from_witness(&outer_circuit, val));
291 }
292 public_inputs_ct.push_back(vec_ct);
293 }
294 // Mutate a PI entry to verify that validation correctly fails with incorrect public inputs
295 public_inputs_ct[1][5] += 1;
296
297 // Construct the AVM recursive verifier and verify the proof
298 // Scoped to free memory of AvmRecursiveVerifier.
299 {
300 std::cout << "Constructing AvmRecursiveVerifier and verifying proof..." << std::endl;
301 std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
302 TwoLayerAvmRecursiveVerifier avm_rec_verifier(outer_circuit);
303 auto result = avm_rec_verifier.verify_proof(stdlib_proof, public_inputs_ct);
304 std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
305 std::cout << "Time taken (recursive verification): "
306 << std::chrono::duration_cast<std::chrono::seconds>(end - start).count() << "s" << std::endl;
307 };
308
309 ASSERT_TRUE(outer_circuit.failed()) << "Outer circuit SHOULD fail with bad PIs.";
310}
311
312} // namespace bb::avm2::constraining
#define BB_ASSERT_GT(left, right,...)
Definition assert.hpp:113
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:83
#define AVM_V2_PROOF_LENGTH_IN_FIELDS_PADDED
static bool check(const Builder &circuit)
Check the witness satisifies the circuit.
UltraCircuitBuilder CircuitBuilder
typename Curve::ScalarField FF
Output verify_proof(const Proof &proof)
Perform ultra verification.
Proof prove(tracegen::TraceContainer &&trace)
static std::pair< stdlib::field_t< Builder >, std::shared_ptr< TemplatedTranscript< Builder > > > hash_avm_transcript_for_testing(Builder &builder, const stdlib::Proof< Builder > &stdlib_proof, const std::vector< std::vector< stdlib::field_t< Builder > > > &public_inputs)
Testing method to hash the transcript after having replicated the operations performed on the AVM tra...
FF hash_avm_transcript(const StdlibProof &stdlib_proof)
Hash the transcript after verification is complete to produce a hash of the public inputs and proofs ...
PairingPoints verify_proof(const StdlibProof &stdlib_proof, const std::vector< std::vector< typename Flavor::FF > > &public_inputs)
Verify an AVM proof and return PairingPoints whose validity bears witness to successful verification ...
bool verify_proof(const HonkProof &proof, const std::vector< std::vector< FF > > &public_inputs)
Verify an AVM proof.
Definition verifier.cpp:52
Recursive verifier of AVM2 proofs that utilizes the Goblin mechanism for efficient EC operations.
TwoLayerAvmRecursiveVerifierOutput verify_proof(const stdlib::Proof< UltraCircuitBuilder > &stdlib_proof, const std::vector< std::vector< UltraFF > > &public_inputs) const
Recursively verify an AVM proof using Goblin and two layers of recursive verification.
typename RecursiveFlavor::CircuitBuilder OuterBuilder
static void create_and_verify_native_proof(NativeProofResult &proof_result)
A simple wrapper around a vector of stdlib field elements representing a proof.
Definition proof.hpp:19
The data that is propagated on the public inputs of a rollup circuit.
#define info(...)
Definition log.hpp:93
#define vinfo(...)
Definition log.hpp:94
AluTraceBuilder builder
Definition alu.test.cpp:124
TestTraceContainer trace
AvmProvingInputs inputs
TEST_P(AvmRecursiveTestsParameterized, TwoLayerAvmRecursion)
A test of the Two Layer AVM recursive verifier.
INSTANTIATE_TEST_SUITE_P(PaddingVariants, AvmRecursiveTestsParameterized, ::testing::Values(false, true), [](const auto &info) { return info.param ? "Padded" :"Unpadded";})
TEST_F(AvmRecursiveTests, TwoLayerAvmRecursionFailsWithWrongPIs)
bool skip_slow_tests()
Check if slow tests should be skipped.
Definition fixtures.cpp:199
std::pair< tracegen::TraceContainer, PublicInputs > get_minimal_trace_with_pi()
Definition fixtures.cpp:183
AvmFlavorSettings::FF FF
Definition field.hpp:10
std::filesystem::path bb_crs_path()
void init_file_crs_factory(const std::filesystem::path &path)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13