Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
eccvm_recursive_verifier.test.cpp
Go to the documentation of this file.
10
11#include <gtest/gtest.h>
12
13namespace {
15}
16namespace bb {
17class ECCVMRecursiveTests : public ::testing::Test {
18 public:
29
32
34
40
43
50 static InnerBuilder generate_circuit(numeric::RNG* engine = nullptr, const size_t num_iterations = 1)
51 {
52 using Curve = curve::BN254;
53 using G1 = Curve::Element;
54 using Fr = Curve::ScalarField;
56
58 G1 a = G1::random_element(engine);
59 G1 b = G1::random_element(engine);
60 G1 c = G1::random_element(engine);
63 for (size_t idx = 0; idx < num_iterations; idx++) {
64 op_queue->add_accumulate(a);
65 op_queue->mul_accumulate(a, x);
66 op_queue->mul_accumulate(b, x);
67 op_queue->mul_accumulate(b, y);
68 op_queue->add_accumulate(a);
69 op_queue->mul_accumulate(b, x);
70 op_queue->eq_and_reset();
71 op_queue->add_accumulate(c);
72 op_queue->mul_accumulate(a, x);
73 op_queue->mul_accumulate(b, x);
74 op_queue->eq_and_reset();
75 op_queue->mul_accumulate(a, x);
76 op_queue->mul_accumulate(b, x);
77 op_queue->mul_accumulate(c, x);
78 op_queue->merge();
79 }
80 // Set hiding op for ECCVM ZK (required before ECCVMCircuitBuilder construction)
81 op_queue->append_hiding_op(Fq::random_element(engine), Fq::random_element(engine));
82 InnerBuilder builder{ op_queue };
83 return builder;
84 }
85
87 {
89 std::shared_ptr<Transcript> prover_transcript = std::make_shared<Transcript>();
90 InnerProver prover(builder, prover_transcript);
91 auto proof = prover.construct_proof();
92
93 auto verification_key = std::make_shared<InnerFlavor::VerificationKey>();
94
95 info("ECCVM Recursive Verifier");
96 OuterBuilder outer_circuit;
97 auto stdlib_proof = stdlib::Proof<OuterBuilder>(outer_circuit, proof);
99 RecursiveVerifier verifier{ stdlib_verifier_transcript, stdlib_proof };
100 verifier.get_transcript()->enable_manifest();
101 [[maybe_unused]] auto recursive_result = verifier.reduce_to_triple_ipa_claim();
103
104 info("Recursive Verifier: num gates = ", outer_circuit.get_num_finalized_gates_inefficient());
105
106 // Check for a failure flag in the recursive verifier circuit
107 EXPECT_EQ(outer_circuit.failed(), false) << outer_circuit.err();
108
109 bool result = CircuitChecker::check(outer_circuit);
110 EXPECT_TRUE(result);
111
112 std::shared_ptr<Transcript> verifier_transcript = std::make_shared<Transcript>();
113 InnerVerifier native_verifier(verifier_transcript, proof);
114 verifier_transcript->enable_manifest();
115 auto native_result = native_verifier.reduce_to_triple_ipa_claim();
116
117 // Verify the TripleIPA proof against the claim emitted by the native verifier
118 auto ipa_verify_transcript = std::make_shared<Transcript>(prover.ipa_proof);
120 auto accumulator =
121 NativeTripleIPA::reduce_to_accumulator(native_result.triple_ipa_claim, ipa_verify_transcript);
122 bool ipa_verified = NativeTripleIPA::verify_accumulator(ipa_vk, accumulator);
123 EXPECT_TRUE(ipa_verified && native_result.reduction_succeeded);
124 auto recursive_manifest = verifier.get_transcript()->get_manifest();
125 auto native_manifest = native_verifier.get_transcript()->get_manifest();
126
127 ASSERT_GT(recursive_manifest.size(), 0);
128 for (size_t i = 0; i < recursive_manifest.size(); ++i) {
129 EXPECT_EQ(recursive_manifest[i], native_manifest[i])
130 << "Recursive Verifier/Verifier manifest discrepency in round " << i;
131 }
132
133 // Ensure verification key commitments are the same
134 for (auto [vk_poly, native_vk_poly] :
135 zip_view(verifier.get_verification_key()->get_all(), verification_key->get_all())) {
136 EXPECT_EQ(vk_poly.get_value(), native_vk_poly);
137 }
138
139 // Construct a full proof from the recursive verifier circuit
140 {
141 auto prover_instance = std::make_shared<OuterProverInstance>(outer_circuit);
142 auto verification_key = std::make_shared<OuterFlavor::VerificationKey>(prover_instance->get_precomputed());
143 auto vk_and_hash = std::make_shared<OuterFlavor::VKAndHash>(verification_key);
144 OuterProver prover(prover_instance, verification_key);
145 OuterVerifier verifier(vk_and_hash);
146 auto proof = prover.construct_proof();
147 bool verified = verifier.verify_proof(proof).result;
148
149 ASSERT_TRUE(verified);
150 }
151
152 // Check that the size of the recursive verifier is consistent with historical expectation
154 << "Ultra-arithmetized ECCVM Recursive verifier gate count changed! Update this value if you are sure this "
155 "is expected.";
156 }
157
159 {
161 builder.op_queue->add_erroneous_equality_op_for_testing();
162 builder.op_queue->merge();
163 std::shared_ptr<Transcript> prover_transcript = std::make_shared<Transcript>();
164 InnerProver prover(builder, prover_transcript);
165 auto proof = prover.construct_proof();
166
167 auto verification_key = std::make_shared<InnerFlavor::VerificationKey>();
168
169 OuterBuilder outer_circuit;
170 auto stdlib_proof = stdlib::Proof<OuterBuilder>(outer_circuit, proof);
171
173 RecursiveVerifier verifier{ stdlib_verifier_transcript, stdlib_proof };
174 [[maybe_unused]] auto output = verifier.reduce_to_triple_ipa_claim();
176 info("Recursive Verifier: estimated num finalized gates = ",
178
179 // Check for a failure flag in the recursive verifier circuit
180 EXPECT_FALSE(CircuitChecker::check(outer_circuit));
181 }
182
189 {
191 std::shared_ptr<Transcript> prover_transcript = std::make_shared<Transcript>();
192 InnerProver prover(builder, prover_transcript);
193 auto proof = prover.construct_proof();
194
195 ASSERT_EQ(proof.size(), InnerFlavor::PROOF_LENGTH);
196
197 StructuredProof<InnerFlavor> structured_proof;
198 auto proof_data = prover.transcript->test_get_proof_data();
199 structured_proof.deserialize(proof_data, /*num_public_inputs=*/0, CONST_ECCVM_LOG_N);
200 structured_proof.serialize(proof_data, CONST_ECCVM_LOG_N);
201
202 auto original_data = prover.transcript->test_get_proof_data();
203 ASSERT_EQ(proof_data.size(), original_data.size());
204 EXPECT_EQ(proof_data, original_data);
205 }
206
207 enum class TamperType {
208 MODIFY_SUMCHECK_UNIVARIATE, // Tests committed sumcheck first-round sum constraint (circuit FAIL)
209 MODIFY_SUMCHECK_EVAL, // Tests Gemini consistency constraint (circuit FAIL)
210 MODIFY_TRIPLE_IPA_CLAIM, // Tests TripleIPA opening (circuit PASS, TripleIPA FAIL)
211 MODIFY_TRANSLATION_EVAL, // Tests translation masking consistency constraint (circuit FAIL)
212 MODIFY_LIBRA_EVAL, // Tests Libra SmallSubgroupIPA consistency constraint (circuit FAIL)
213 END
214 };
215
216 static void tamper_eccvm_proof(InnerProver& prover,
217 typename InnerFlavor::Transcript::Proof& proof,
218 TamperType tamper_type)
219 {
220 using FF = InnerFF;
221 static constexpr size_t FIRST_WITNESS_INDEX = InnerFlavor::NUM_PRECOMPUTED_ENTITIES;
222
223 StructuredProof<InnerFlavor> structured_proof;
224 structured_proof.deserialize(
225 prover.transcript->test_get_proof_data(), /*num_public_inputs=*/0, CONST_ECCVM_LOG_N);
226
227 switch (tamper_type) {
229 // Committed sumcheck: break the first-round sum by modifying eval_0 without compensating eval_1.
230 // Preserving the sum would only break TripleIPA opening (external), not any in-circuit constraint.
231 structured_proof.sumcheck_round_eval_0s[0] += FF::random_element();
232 break;
234 structured_proof.sumcheck_evaluations[FIRST_WITNESS_INDEX] = FF::random_element();
235 break;
237 // Modify the final Shplonk Q commitment — bypasses circuit constraints but corrupts TripleIPA opening
238 // claim.
239 structured_proof.final_shplonk_q_comm = structured_proof.final_shplonk_q_comm * FF(2);
240 break;
242 structured_proof.translation_op_eval = FF::random_element();
243 break;
245 structured_proof.libra_quotient_eval = FF::random_element();
246 break;
247 case TamperType::END:
248 break;
249 }
250
251 structured_proof.serialize(prover.transcript->test_get_proof_data(), CONST_ECCVM_LOG_N);
252 prover.transcript->test_set_proof_parsing_state(0, InnerFlavor::PROOF_LENGTH);
253 proof = prover.export_proof();
254 }
255
257 {
258 for (size_t idx = 0; idx < static_cast<size_t>(TamperType::END); idx++) {
259 TamperType tamper_type = static_cast<TamperType>(idx);
260
262 std::shared_ptr<Transcript> prover_transcript = std::make_shared<Transcript>();
263 InnerProver prover(builder, prover_transcript);
264 auto proof = prover.construct_proof();
265
266 // The genuine TripleIPA proof (needed for MODIFY_TRIPLE_IPA_CLAIM case)
267 HonkProof ipa_proof = prover.ipa_proof;
268
269 // Tamper with the proof
270 tamper_eccvm_proof(prover, proof, tamper_type);
271
272 OuterBuilder outer_circuit;
273 auto stdlib_proof = stdlib::Proof<OuterBuilder>(outer_circuit, proof);
275 RecursiveVerifier verifier{ stdlib_verifier_transcript, stdlib_proof };
276 [[maybe_unused]] auto recursive_result = verifier.reduce_to_triple_ipa_claim();
278
279 if (tamper_type == TamperType::MODIFY_TRIPLE_IPA_CLAIM) {
280 // Modifying the final Shplonk Q bypasses circuit constraints but causes IPA failure
281 EXPECT_TRUE(CircuitChecker::check(outer_circuit));
282
283 // Verify that the TripleIPA fails: the tampered Shplonk Q corrupts the univariate claim, so the
284 // claim emitted by the verifier no longer matches the genuine TripleIPA proof
285 std::shared_ptr<Transcript> native_transcript = std::make_shared<Transcript>();
286 InnerVerifier native_verifier(native_transcript, proof);
287 auto native_result = native_verifier.reduce_to_triple_ipa_claim();
288 auto ipa_verify_transcript = std::make_shared<Transcript>(ipa_proof);
290 auto accumulator =
291 NativeTripleIPA::reduce_to_accumulator(native_result.triple_ipa_claim, ipa_verify_transcript);
292 EXPECT_FALSE(NativeTripleIPA::verify_accumulator(ipa_vk, accumulator));
293 } else {
294 // All other tamper types should cause a circuit constraint violation
295 EXPECT_FALSE(CircuitChecker::check(outer_circuit)) << "Expected circuit failure for TamperType " << idx;
296 }
297 }
298 }
299
301 {
302
303 // Retrieves the trace blocks (each consisting of a specific gate) from the recursive verifier circuit
304 auto get_blocks = [](size_t inner_size)
306 auto inner_circuit = generate_circuit(&engine, inner_size);
307 std::shared_ptr<Transcript> prover_transcript = std::make_shared<Transcript>();
308 InnerProver inner_prover(inner_circuit, prover_transcript);
309
310 auto proof = inner_prover.construct_proof();
311
312 // Create a recursive verification circuit for the proof of the inner circuit
313 OuterBuilder outer_circuit;
314 auto stdlib_proof = stdlib::Proof<OuterBuilder>(outer_circuit, proof);
315
317 RecursiveVerifier verifier{ stdlib_verifier_transcript, stdlib_proof };
318
319 [[maybe_unused]] auto recursive_triple_ipa_claim = verifier.reduce_to_triple_ipa_claim();
321
322 auto outer_proving_key = std::make_shared<OuterProverInstance>(outer_circuit);
323 auto outer_verification_key =
324 std::make_shared<OuterFlavor::VerificationKey>(outer_proving_key->get_precomputed());
325
326 return { outer_circuit.blocks, outer_verification_key };
327 };
328
329 auto [blocks_20, verification_key_20] = get_blocks(20);
330 auto [blocks_40, verification_key_40] = get_blocks(40);
331
332 compare_ultra_blocks_and_verification_keys<OuterFlavor>({ blocks_20, blocks_40 },
333 { verification_key_20, verification_key_40 });
334 };
335};
336
341
342TEST_F(ECCVMRecursiveTests, SingleRecursiveVerificationFailure)
343{
345};
346
351
356
361
362} // namespace bb
bb::field< bb::Bn254FrParams > FF
Definition field.cpp:24
Common transcript class for both parties. Stores the data for the current round, as well as the manif...
const std::string & err() const
The proving key is responsible for storing the polynomials used by the prover.
static constexpr size_t ECCVM_FIXED_SIZE
typename Curve::ScalarField FF
ECCVMCircuitBuilder CircuitBuilder
typename G1::affine_element Commitment
typename Curve::BaseField BF
static constexpr size_t PROOF_LENGTH
static constexpr size_t NUM_PRECOMPUTED_ENTITIES
FixedVKAndHash_< PrecomputedEntities< Commitment >, BF, ECCVMHardcodedVKAndHash > VerificationKey
The verification key stores commitments to the precomputed polynomials used by the verifier.
BaseTranscript< Codec, HashFunction > Transcript
std::shared_ptr< Transcript > transcript
StdlibTranscript< CircuitBuilder > Transcript
static void tamper_eccvm_proof(InnerProver &prover, typename InnerFlavor::Transcript::Proof &proof, TamperType tamper_type)
static InnerBuilder generate_circuit(numeric::RNG *engine=nullptr, const size_t num_iterations=1)
Adds operations in BN254 to the op_queue and then constructs and ECCVM circuit from the op_queue.
static void test_structured_proof_round_trip()
Verify that StructuredProof<ECCVMFlavor> can round-trip serialize/deserialize a proof.
std::conditional_t< IsMegaBuilder< OuterBuilder >, MegaFlavor, UltraFlavor > OuterFlavor
Unified ECCVM verifier class for both native and recursive verification.
ReductionResult reduce_to_triple_ipa_claim()
Reduce the ECCVM proof to a compact TripleIPA verifier claim.
bb::TripleIPA< Curve, CONST_ECCVM_LOG_N > TripleIPA
std::shared_ptr< Transcript > get_transcript() const
Simple verification key class for fixed-size circuits (ECCVM, Translator, AVM).
Definition flavor.hpp:104
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.
static NativeAccumulator reduce_to_accumulator(const TripleIpaClaim &claim, const auto &transcript)
static bool verify_accumulator(const VK &vk, const NativeAccumulator &accumulator)
size_t get_num_finalized_gates() const override
Get the number of gates in a finalized circuit.
size_t get_num_finalized_gates_inefficient() const
Get the number of gates in the finalized version of the circuit.
Output verify_proof(const Proof &proof)
Perform ultra verification.
Representation of the Grumpkin Verifier Commitment Key inside a bn254 circuit.
typename Group::element Element
Definition grumpkin.hpp:63
A simple wrapper around a vector of stdlib field elements representing a proof.
Definition proof.hpp:20
static void add_default(Builder &builder)
Add default public inputs when they are not present.
#define info(...)
Definition log.hpp:93
AluTraceBuilder builder
Definition alu.test.cpp:124
FF a
FF b
numeric::RNG & engine
constexpr size_t ECCVM_RECURSIVE_VERIFIER_GATE_COUNT
RNG & get_debug_randomness(bool reset, std::uint_fast64_t seed)
Definition engine.cpp:245
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
std::vector< fr > HonkProof
Definition proof.hpp:15
ECCVMVerifier_< ECCVMRecursiveFlavor > ECCVMRecursiveVerifier
TEST_F(IPATest, ChallengesAreZero)
Definition ipa.test.cpp:160
ECCVMVerifier_< ECCVMFlavor > ECCVMVerifier
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
Curve::AffineElement G1
Test utility for deserializing/serializing proof data into typed structures.
static field random_element(numeric::RNG *engine=nullptr) noexcept
VectorField result