Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
translator_recursive_verifier.test.cpp
Go to the documentation of this file.
12#include <gtest/gtest.h>
13namespace bb {
14
21class TranslatorRecursiveTests : public ::testing::Test {
22 public:
32
34
40
42
44
46
47 // Helper function to add no-ops
48 static void add_random_ops(std::shared_ptr<bb::ECCOpQueue>& op_queue, size_t count)
49 {
50 for (size_t i = 0; i < count; i++) {
51 op_queue->random_op_ultra_only();
52 }
53 }
54
55 // Helper function to create an MSM
56 static void add_mixed_ops(std::shared_ptr<bb::ECCOpQueue>& op_queue, size_t count = 100)
57 {
58 auto P1 = InnerG1::random_element();
59 auto P2 = InnerG1::random_element();
60 auto z = InnerFF::random_element();
61 for (size_t i = 0; i < count; i++) {
62 op_queue->add_accumulate(P1);
63 op_queue->mul_accumulate(P2, z);
64 }
65 op_queue->eq_and_reset();
66 }
67
68 // Construct a test circuit based on some random operations
69 static InnerBuilder generate_test_circuit(const InnerBF& batching_challenge_v,
70 const InnerBF& evaluation_challenge_x,
71 const size_t circuit_size_parameter = 500)
72 {
73
74 // Add the same operations to the ECC op queue; the native computation is performed under the hood.
75 auto op_queue = std::make_shared<bb::ECCOpQueue>();
76 op_queue->no_op_ultra_only();
78 add_mixed_ops(op_queue, circuit_size_parameter / 2);
79 op_queue->merge();
80 add_mixed_ops(op_queue, circuit_size_parameter / 2);
82 op_queue->merge(MergeSettings::APPEND, ECCOpQueue::OP_QUEUE_SIZE - op_queue->get_current_subtable_size());
83
84 return InnerBuilder{ batching_challenge_v, evaluation_challenge_x, op_queue };
85 }
86
87 // Helper to create native op queue commitments from proving key
88 static std::array<InnerFlavor::Commitment, InnerFlavor::NUM_OP_QUEUE_WIRES> create_native_op_queue_commitments(
90 {
91 std::array<InnerFlavor::Commitment, InnerFlavor::NUM_OP_QUEUE_WIRES> op_queue_commitments;
92 op_queue_commitments[0] =
93 proving_key->proving_key->commitment_key.commit(proving_key->proving_key->polynomials.op);
94 op_queue_commitments[1] =
95 proving_key->proving_key->commitment_key.commit(proving_key->proving_key->polynomials.x_lo_y_hi);
96 op_queue_commitments[2] =
97 proving_key->proving_key->commitment_key.commit(proving_key->proving_key->polynomials.x_hi_z_1);
98 op_queue_commitments[3] =
99 proving_key->proving_key->commitment_key.commit(proving_key->proving_key->polynomials.y_lo_z_2);
100 return op_queue_commitments;
101 }
102
103 // Helper to convert native op queue commitments to stdlib commitments
104 static std::array<RecursiveFlavor::Commitment, InnerFlavor::NUM_OP_QUEUE_WIRES> create_stdlib_op_queue_commitments(
105 OuterBuilder* builder, const std::array<InnerFlavor::Commitment, InnerFlavor::NUM_OP_QUEUE_WIRES>& native_comms)
106 {
107 std::array<RecursiveFlavor::Commitment, InnerFlavor::NUM_OP_QUEUE_WIRES> stdlib_comms;
108 for (size_t i = 0; i < InnerFlavor::NUM_OP_QUEUE_WIRES; i++) {
109 stdlib_comms[i] = RecursiveFlavor::Commitment::from_witness(builder, native_comms[i]);
110 // Set empty origin tags for commitments (they're free witnesses from merge protocol)
111 stdlib_comms[i].set_origin_tag(OriginTag::constant());
112 }
113 return stdlib_comms;
114 }
115
116 // Helper struct to hold translator verification inputs as stdlib witnesses
121 std::array<RecursiveFlavor::Commitment, InnerFlavor::NUM_OP_QUEUE_WIRES> op_queue_commitments;
122 // Native values for native verifier
124 std::array<InnerFlavor::Commitment, InnerFlavor::NUM_OP_QUEUE_WIRES> native_op_queue_commitments;
125 };
126
127 // Helper to create recursive verifier inputs from native values
129 const InnerProver& prover,
130 const InnerBF& evaluation_challenge_x,
131 const InnerBF& batching_challenge_v)
132 {
133 // Get accumulated_result from the prover
134 bb::fq accumulated_result_native = prover.get_accumulated_result();
135 auto accumulated_result = TranslatorBF::from_witness(builder, accumulated_result_native);
136 accumulated_result.set_origin_tag(OriginTag::constant());
137
138 // Convert challenges to circuit witnesses
139 auto stdlib_evaluation_challenge_x = TranslatorBF::from_witness(builder, evaluation_challenge_x);
140 auto stdlib_batching_challenge_v = TranslatorBF::from_witness(builder, batching_challenge_v);
141 stdlib_evaluation_challenge_x.set_origin_tag(OriginTag::constant());
142 stdlib_batching_challenge_v.set_origin_tag(OriginTag::constant());
143
144 // Create op queue commitments (normally provided by merge protocol)
145 auto native_op_queue_commitments = create_native_op_queue_commitments(prover.key);
146 auto op_queue_commitments = create_stdlib_op_queue_commitments(builder, native_op_queue_commitments);
147
148 return { accumulated_result, stdlib_evaluation_challenge_x, stdlib_batching_challenge_v,
149 op_queue_commitments, accumulated_result_native, native_op_queue_commitments };
150 }
151
152 // Shared helper to create and verify a translator proof recursively
153 // Includes native verification and consistency checks
155 size_t circuit_size_parameter = 500)
156 {
157
158 // Create fake ECCVM proof
159 auto prover_transcript = std::make_shared<Transcript>();
160
161 // Generate challenges
162 InnerBF batching_challenge_v = InnerBF::random_element();
163 InnerBF evaluation_challenge_x = InnerBF::random_element();
164
165 // Create inner translator circuit and generate proof
166 InnerBuilder circuit_builder =
167 generate_test_circuit(batching_challenge_v, evaluation_challenge_x, circuit_size_parameter);
168 auto proving_key = std::make_shared<TranslatorProvingKey>(circuit_builder);
169 InnerProver prover{ proving_key, prover_transcript };
170 auto proof = prover.construct_proof();
171
172 // Set up outer recursive circuit
173 OuterBuilder outer_circuit;
174 stdlib::Proof<OuterBuilder> stdlib_proof(outer_circuit, proof);
175 auto transcript = std::make_shared<RecursiveFlavor::Transcript>(stdlib_proof);
176
177 // Create recursive verifier inputs
178 auto recursive_inputs =
179 create_recursive_verifier_inputs(&outer_circuit, prover, evaluation_challenge_x, batching_challenge_v);
180
181 // Verify proof recursively
182 stdlib::Proof<OuterBuilder> stdlib_proof_for_verifier(outer_circuit, proof);
183 RecursiveVerifier verifier{ transcript,
184 stdlib_proof_for_verifier,
185 recursive_inputs.evaluation_challenge_x,
186 recursive_inputs.batching_challenge_v,
187 recursive_inputs.accumulated_result,
188 recursive_inputs.op_queue_commitments };
189 auto recursive_result = verifier.reduce_to_pairing_check();
190
192 inputs.pairing_inputs = recursive_result.pairing_points;
194
195 // Verify with native verifier and compare results
196 auto native_verifier_transcript = std::make_shared<Transcript>(proof);
197 InnerVerifier native_verifier(native_verifier_transcript,
198 proof,
199 evaluation_challenge_x,
200 batching_challenge_v,
201 recursive_inputs.accumulated_result_native,
202 recursive_inputs.native_op_queue_commitments);
203 auto native_result = native_verifier.reduce_to_pairing_check();
204 bool native_verified = native_result.pairing_points.check() && native_result.reduction_succeeded;
205
206 auto recursive_verified = recursive_result.pairing_points.check();
207 EXPECT_EQ(recursive_verified, native_verified);
208
209 // Verify VK commitments consistency between recursive and native verifiers
210 auto recursive_vk = verifier.get_verification_key();
211 auto native_vk = native_verifier.get_verification_key();
212 for (auto [vk_poly, native_vk_poly] : zip_view(recursive_vk->get_all(), native_vk->get_all())) {
213 EXPECT_EQ(vk_poly.get_value(), native_vk_poly);
214 }
215
216 auto outer_proving_key = std::make_shared<OuterProverInstance>(outer_circuit);
217 auto outer_verification_key =
218 std::make_shared<typename OuterFlavor::VerificationKey>(outer_proving_key->get_precomputed());
219
220 return { std::move(outer_circuit), outer_verification_key };
221 }
222
224 {
225 // Use the shared helper to create and verify the recursive circuit
226 auto [outer_circuit, outer_verification_key] = create_recursive_verifier_circuit();
227
228 info("Recursive Verifier: num gates = ", outer_circuit.get_num_finalized_gates_inefficient());
229 EXPECT_EQ(outer_circuit.failed(), false) << outer_circuit.err();
230
231 // Prove and verify the outer recursive circuit
232 auto prover_instance = std::make_shared<OuterProverInstance>(outer_circuit);
233 auto vk_and_hash = std::make_shared<OuterFlavor::VKAndHash>(outer_verification_key);
234 OuterProver prover(prover_instance, outer_verification_key);
235 OuterVerifier verifier(vk_and_hash);
236 auto proof = prover.construct_proof();
237 bool verified = verifier.verify_proof(proof).result;
238
239 ASSERT_TRUE(verified);
240 }
241
248 {
249 InnerBF batching_challenge_v = InnerBF::random_element();
250 InnerBF evaluation_challenge_x = InnerBF::random_element();
251
252 InnerBuilder circuit_builder = generate_test_circuit(batching_challenge_v, evaluation_challenge_x);
253 auto prover_transcript = std::make_shared<Transcript>();
254 auto proving_key = std::make_shared<TranslatorProvingKey>(circuit_builder);
255 InnerProver prover{ proving_key, prover_transcript };
256 auto proof = prover.construct_proof();
257
258 ASSERT_EQ(proof.size(), InnerFlavor::PROOF_LENGTH);
259
260 StructuredProof<InnerFlavor> structured_proof;
261 auto proof_data = prover.transcript->test_get_proof_data();
262 structured_proof.deserialize(proof_data, /*num_public_inputs=*/0, InnerFlavor::CONST_TRANSLATOR_LOG_N);
263 structured_proof.serialize(proof_data, InnerFlavor::CONST_TRANSLATOR_LOG_N);
264
265 auto original_data = prover.transcript->test_get_proof_data();
266 ASSERT_EQ(proof_data.size(), original_data.size());
267 EXPECT_EQ(proof_data, original_data);
268 }
269
270 enum class TamperType {
271 MODIFY_SUMCHECK_UNIVARIATE, // Tests sumcheck round consistency constraint (circuit FAIL)
272 MODIFY_SUMCHECK_EVAL, // Tests final relation check constraint (circuit FAIL)
273 MODIFY_KZG_WITNESS, // Tests pairing check (circuit PASS, pairing FAIL)
274 MODIFY_LIBRA_EVAL, // Tests Libra consistency constraint (circuit FAIL)
275 END
276 };
277
279 typename InnerFlavor::Transcript::Proof& proof,
280 TamperType tamper_type)
281 {
282 using FF = InnerFF;
283 static constexpr size_t LOG_N = InnerFlavor::CONST_TRANSLATOR_LOG_N;
284
285 StructuredProof<InnerFlavor> structured_proof;
286 structured_proof.deserialize(prover.transcript->test_get_proof_data(), /*num_public_inputs=*/0, LOG_N);
287
288 switch (tamper_type) {
290 FF delta = FF::random_element();
291 structured_proof.sumcheck_univariates[0].value_at(0) += delta;
292 structured_proof.sumcheck_univariates[0].value_at(1) -= delta;
293 break;
294 }
296 structured_proof.full_circuit_evaluations[0] = FF::random_element();
297 break;
299 structured_proof.kzg_w_comm = structured_proof.kzg_w_comm * FF::random_element();
300 break;
302 structured_proof.libra_quotient_eval = FF::random_element();
303 break;
304 case TamperType::END:
305 break;
306 }
307
308 structured_proof.serialize(prover.transcript->test_get_proof_data(), LOG_N);
309 prover.transcript->test_set_proof_parsing_state(0, InnerFlavor::PROOF_LENGTH);
310 proof = prover.export_proof();
311 }
312
314 {
315 for (size_t idx = 0; idx < static_cast<size_t>(TamperType::END); idx++) {
316 TamperType tamper_type = static_cast<TamperType>(idx);
317
318 // Generate challenges
319 InnerBF batching_challenge_v = InnerBF::random_element();
320 InnerBF evaluation_challenge_x = InnerBF::random_element();
321
322 // Create inner translator circuit and generate proof
323 InnerBuilder circuit_builder = generate_test_circuit(batching_challenge_v, evaluation_challenge_x);
324 auto proving_key = std::make_shared<TranslatorProvingKey>(circuit_builder);
325 auto prover_transcript = std::make_shared<Transcript>();
326 InnerProver prover{ proving_key, prover_transcript };
327 auto proof = prover.construct_proof();
328
329 // Tamper with the proof
330 tamper_translator_proof(prover, proof, tamper_type);
331
332 // Set up outer recursive circuit
333 OuterBuilder outer_circuit;
334
335 // Create recursive verifier inputs (unaffected by proof tampering)
336 auto recursive_inputs =
337 create_recursive_verifier_inputs(&outer_circuit, prover, evaluation_challenge_x, batching_challenge_v);
338
339 // Create recursive verifier and verify tampered proof
340 stdlib::Proof<OuterBuilder> stdlib_proof(outer_circuit, proof);
341 auto transcript = std::make_shared<RecursiveFlavor::Transcript>(stdlib_proof);
342 stdlib::Proof<OuterBuilder> stdlib_proof_for_verifier(outer_circuit, proof);
343 RecursiveVerifier verifier{ transcript,
344 stdlib_proof_for_verifier,
345 recursive_inputs.evaluation_challenge_x,
346 recursive_inputs.batching_challenge_v,
347 recursive_inputs.accumulated_result,
348 recursive_inputs.op_queue_commitments };
349 auto recursive_result = verifier.reduce_to_pairing_check();
350
351 if (tamper_type == TamperType::MODIFY_KZG_WITNESS) {
352 // KZG witness tampering bypasses circuit constraints but causes pairing failure
353 EXPECT_TRUE(CircuitChecker::check(outer_circuit));
354 EXPECT_FALSE(recursive_result.pairing_points.check());
355 } else {
356 // All other tamper types should cause a circuit constraint violation
357 EXPECT_FALSE(CircuitChecker::check(outer_circuit));
358 }
359 }
360 }
361
363 {
364 auto [outer_circuit_256, verification_key_256] = create_recursive_verifier_circuit(256);
365 auto [outer_circuit_512, verification_key_512] = create_recursive_verifier_circuit(512);
366
367 compare_ultra_blocks_and_verification_keys<OuterFlavor>({ outer_circuit_256.blocks, outer_circuit_512.blocks },
368 { verification_key_256, verification_key_512 });
369 };
370
377 {
378 auto prover_transcript = std::make_shared<Transcript>();
379
380 InnerBF batching_challenge_v = InnerBF::random_element();
381 InnerBF evaluation_challenge_x = InnerBF::random_element();
382
383 InnerBuilder circuit_builder = generate_test_circuit(batching_challenge_v, evaluation_challenge_x);
384 auto proving_key = std::make_shared<TranslatorProvingKey>(circuit_builder);
385 InnerProver prover{ proving_key, prover_transcript };
386 auto proof = prover.construct_proof();
387
388 // Set up outer recursive circuit
389 OuterBuilder outer_circuit;
390 stdlib::Proof<OuterBuilder> stdlib_proof(outer_circuit, proof);
391 auto transcript = std::make_shared<RecursiveFlavor::Transcript>(stdlib_proof);
392
393 // Create recursive verifier inputs
394 auto recursive_inputs =
395 create_recursive_verifier_inputs(&outer_circuit, prover, evaluation_challenge_x, batching_challenge_v);
396
397 // Verify proof recursively
398 stdlib::Proof<OuterBuilder> stdlib_proof_for_verifier(outer_circuit, proof);
399 RecursiveVerifier verifier{ transcript,
400 stdlib_proof_for_verifier,
401 recursive_inputs.evaluation_challenge_x,
402 recursive_inputs.batching_challenge_v,
403 recursive_inputs.accumulated_result,
404 recursive_inputs.op_queue_commitments };
405 auto recursive_result = verifier.reduce_to_pairing_check();
406
407 recursive_result.pairing_points.fix_witness();
408
410 inputs.pairing_inputs = recursive_result.pairing_points;
412
413 EXPECT_EQ(outer_circuit.failed(), false) << outer_circuit.err();
414 outer_circuit.finalize_circuit(false);
415
416 // Run static analysis - no variable should appear in only one gate
417 auto graph = cdg::UltraStaticAnalyzer(outer_circuit);
418 auto [cc, variables_in_one_gate] = graph.analyze_circuit(/*filter_cc=*/true);
419
420 EXPECT_EQ(variables_in_one_gate.size(), 0);
421 }
422};
423
428
433
438
443
448} // namespace bb
Common transcript class for both parties. Stores the data for the current round, as well as the manif...
const std::string & err() const
static const size_t OP_QUEUE_SIZE
Contains all the information required by a Honk prover to create a proof, constructed from a finalize...
TranslatorCircuitBuilder creates a circuit that evaluates the correctness of the evaluation of EccOpQ...
static bool check(const Builder &circuit)
Check the witness satisifies the circuit.
BaseTranscript< Codec, HashFunction > Transcript
static constexpr size_t CONST_TRANSLATOR_LOG_N
static constexpr size_t NUM_OP_QUEUE_WIRES
static constexpr size_t PROOF_LENGTH
TranslatorCircuitBuilder CircuitBuilder
Curve::ScalarField FF
Curve::AffineElement Commitment
uint256_t get_accumulated_result() const
Extract the accumulated result from the circuit.
std::shared_ptr< TranslatorProvingKey > key
std::shared_ptr< Transcript > transcript
The recursive counterpart of the native Translator flavor.
Test suite for standalone recursive verification of translation proofs.
static void add_mixed_ops(std::shared_ptr< bb::ECCOpQueue > &op_queue, size_t count=100)
static InnerBuilder generate_test_circuit(const InnerBF &batching_challenge_v, const InnerBF &evaluation_challenge_x, const size_t circuit_size_parameter=500)
std::conditional_t< IsMegaBuilder< OuterBuilder >, MegaFlavor, UltraFlavor > OuterFlavor
static RecursiveVerifierInputs create_recursive_verifier_inputs(OuterBuilder *builder, const InnerProver &prover, const InnerBF &evaluation_challenge_x, const InnerBF &batching_challenge_v)
static void test_static_analysis()
Static analysis (boomerang detection) of the translator recursive verifier circuit.
static void add_random_ops(std::shared_ptr< bb::ECCOpQueue > &op_queue, size_t count)
static void test_structured_proof_round_trip()
Verify that StructuredProof<TranslatorFlavor> can round-trip serialize/deserialize a proof.
static std::array< RecursiveFlavor::Commitment, InnerFlavor::NUM_OP_QUEUE_WIRES > create_stdlib_op_queue_commitments(OuterBuilder *builder, const std::array< InnerFlavor::Commitment, InnerFlavor::NUM_OP_QUEUE_WIRES > &native_comms)
static std::tuple< OuterBuilder, std::shared_ptr< OuterFlavor::VerificationKey > > create_recursive_verifier_circuit(size_t circuit_size_parameter=500)
static std::array< InnerFlavor::Commitment, InnerFlavor::NUM_OP_QUEUE_WIRES > create_native_op_queue_commitments(const std::shared_ptr< TranslatorProvingKey > &proving_key)
static void tamper_translator_proof(InnerProver &prover, typename InnerFlavor::Transcript::Proof &proof, TamperType tamper_type)
Translator verifier class that verifies the proof of the Translator circuit.
ReductionResult reduce_to_pairing_check()
Reduce the translator proof to a pairing check.
std::shared_ptr< VerificationKey > get_verification_key() const
Get the verification key.
void finalize_circuit(const bool ensure_nonzero)
Output verify_proof(const Proof &proof)
Perform ultra verification.
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.
#define info(...)
Definition log.hpp:93
AluTraceBuilder builder
Definition alu.test.cpp:124
AvmProvingInputs inputs
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
TranslatorVerifier_< TranslatorRecursiveFlavor > TranslatorRecursiveVerifier
TEST_F(IPATest, ChallengesAreZero)
Definition ipa.test.cpp:155
TranslatorVerifier_< TranslatorFlavor > TranslatorVerifier
StaticAnalyzer_< bb::fr, bb::UltraCircuitBuilder > UltraStaticAnalyzer
Definition graph.hpp:188
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
This file contains part of the logic for the Origin Tag mechanism that tracks the use of in-circuit p...
static OriginTag constant()
Test utility for deserializing/serializing proof data into typed structures.
std::array< RecursiveFlavor::Commitment, InnerFlavor::NUM_OP_QUEUE_WIRES > op_queue_commitments
std::array< InnerFlavor::Commitment, InnerFlavor::NUM_OP_QUEUE_WIRES > native_op_queue_commitments
static field random_element(numeric::RNG *engine=nullptr) noexcept
uint32_t set_public(Builder *ctx=nullptr)
Set the witness indices for the pairing points to public.