Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
chonk.test.cpp
Go to the documentation of this file.
1#include <ranges>
2
17#include "gtest/gtest.h"
18
19using namespace bb;
20
21static constexpr size_t SMALL_LOG_2_NUM_GATES = 5;
22
23class ChonkTests : public ::testing::Test {
24 protected:
26
28 using FF = typename Flavor::FF;
35 using CircuitProducer = PrivateFunctionExecutionMockCircuitProducer;
37
38 public:
45 static void tamper_with_proof(HonkProof& proof, size_t public_inputs_offset)
46 {
47 // Tamper with the commitment in the proof
48 Commitment commitment = FrCodec::deserialize_from_fields<Commitment>(
49 std::span{ proof }.subspan(public_inputs_offset, FrCodec::template calc_num_fields<Commitment>()));
50 commitment = commitment + Commitment::one();
51 auto commitment_frs = FrCodec::serialize_to_fields<Commitment>(commitment);
52 for (size_t idx = 0; idx < 4; ++idx) {
53 proof[public_inputs_offset + idx] = commitment_frs[idx];
54 }
55 }
56
58 size_t num_app_circuits, TestSettings settings = {}, bool check_circuit_sizes = false)
59 {
60 CircuitProducer circuit_producer(num_app_circuits);
61 const size_t num_circuits = circuit_producer.total_num_circuits;
62 Chonk ivc{ num_circuits };
63
64 for (size_t j = 0; j < num_circuits; ++j) {
65 circuit_producer.construct_and_accumulate_next_circuit(ivc, settings, check_circuit_sizes);
66 }
67 return { ivc.prove(), ivc.get_hiding_kernel_vk_and_hash() };
68 };
69
70 static bool verify_chonk(const ChonkProof& proof, const std::shared_ptr<MegaZKFlavor::VKAndHash>& vk_and_hash)
71 {
72 ChonkVerifier verifier(vk_and_hash);
73 return verifier.verify(proof);
74 }
75
80
85
92 {
94
95 const size_t NUM_APP_CIRCUITS = 2;
96 CircuitProducer circuit_producer(NUM_APP_CIRCUITS);
97 const size_t NUM_CIRCUITS = circuit_producer.total_num_circuits;
98 Chonk ivc{ NUM_CIRCUITS };
99 TestSettings settings{ .log2_num_gates = SMALL_LOG_2_NUM_GATES };
100
101 for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) {
102 auto [circuit, vk] = circuit_producer.create_next_circuit_and_vk(ivc, settings);
103 ivc.accumulate(circuit, vk);
104
105 // After accumulating 3 circuits (app, kernel, app), we have 2 proofs in the queue
106 if (idx == 2) {
107 EXPECT_EQ(ivc.verification_queue.size(), 2);
108
109 auto& app_entry = ivc.verification_queue[1];
110 ASSERT_FALSE(app_entry.is_kernel) << "Expected second queue entry to be an app";
111
113 size_t num_public_inputs = app_entry.honk_vk->num_public_inputs;
114 AppIOSerde app_io = AppIOSerde::from_proof(app_entry.proof, num_public_inputs);
115
116 // Double the pairing points (multiply by 2) - creates valid but different points
117 app_io.pairing_inputs.P0 = app_io.pairing_inputs.P0 + app_io.pairing_inputs.P0;
118 app_io.pairing_inputs.P1 = app_io.pairing_inputs.P1 + app_io.pairing_inputs.P1;
119
120 EXPECT_TRUE(app_io.pairing_inputs.check());
121
122 app_io.to_proof(app_entry.proof, num_public_inputs);
123 }
124 }
125
126 auto proof = ivc.prove();
127 EXPECT_FALSE(verify_chonk(proof, ivc.get_hiding_kernel_vk_and_hash()));
128 }
129
135 static void test_kernel_io_tampering(KernelIOField field_to_tamper)
136 {
138
139 const size_t NUM_APP_CIRCUITS = 2;
140 CircuitProducer circuit_producer(NUM_APP_CIRCUITS);
141 const size_t NUM_CIRCUITS = circuit_producer.total_num_circuits;
142 Chonk ivc{ NUM_CIRCUITS };
143 TestSettings settings{ .log2_num_gates = SMALL_LOG_2_NUM_GATES };
144
145 for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) {
146 auto [circuit, vk] = circuit_producer.create_next_circuit_and_vk(ivc, settings);
147 ivc.accumulate(circuit, vk);
148
149 // After accumulating 3 circuits (app, kernel, app), we have 2 proofs in the queue
150 if (idx == 2) {
151 EXPECT_EQ(ivc.verification_queue.size(), 2);
152
153 auto& kernel_entry = ivc.verification_queue[0];
154 ASSERT_TRUE(kernel_entry.is_kernel) << "Expected first queue entry to be a kernel";
155
156 using KernelIOSerde = bb::stdlib::recursion::honk::KernelIOSerde;
157 size_t num_public_inputs = kernel_entry.honk_vk->num_public_inputs;
158 KernelIOSerde kernel_io = KernelIOSerde::from_proof(kernel_entry.proof, num_public_inputs);
159
160 // Tamper with the specified field
161 switch (field_to_tamper) {
163 // Replace with valid default pairing points (different from actual accumulated values)
164 using namespace bb::stdlib::recursion;
165 kernel_io.pairing_inputs.P0 = Commitment(DEFAULT_PAIRING_POINTS_P0_X, DEFAULT_PAIRING_POINTS_P0_Y);
166 kernel_io.pairing_inputs.P1 = Commitment(DEFAULT_PAIRING_POINTS_P1_X, DEFAULT_PAIRING_POINTS_P1_Y);
167 EXPECT_TRUE(kernel_io.pairing_inputs.check());
168 break;
169 }
171 kernel_io.output_hn_accum_hash += FF(1);
172 break;
174 kernel_io.kernel_return_data = kernel_io.kernel_return_data + Commitment::one();
175 break;
177 kernel_io.app_return_data = kernel_io.app_return_data + Commitment::one();
178 break;
180 kernel_io.ecc_op_tables[0] = kernel_io.ecc_op_tables[0] + Commitment::one();
181 break;
182 }
183
184 kernel_io.to_proof(kernel_entry.proof, num_public_inputs);
185 }
186 }
187
188 auto proof = ivc.prove();
189 EXPECT_FALSE(verify_chonk(proof, ivc.get_hiding_kernel_vk_and_hash()));
190 }
191
203 {
204 using HidingKernelIOSerde = bb::stdlib::recursion::honk::HidingKernelIOSerde;
205
206 const size_t NUM_APP_CIRCUITS = 2;
207 CircuitProducer circuit_producer(NUM_APP_CIRCUITS);
208 const size_t NUM_CIRCUITS = circuit_producer.total_num_circuits;
209 Chonk ivc{ NUM_CIRCUITS };
210 TestSettings settings{ .log2_num_gates = SMALL_LOG_2_NUM_GATES };
211
212 // Accumulate all circuits
213 for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) {
214 auto [circuit, vk] = circuit_producer.create_next_circuit_and_vk(ivc, settings);
215 ivc.accumulate(circuit, vk);
216 }
217
218 // Extract field from Tail kernel's proof before prove() generates HidingKernel
219 HidingKernelIOSerde tail_io;
220 for (auto& it : std::ranges::reverse_view(ivc.verification_queue)) {
221 if (it.is_kernel) {
222 size_t num_public_inputs = it.honk_vk->num_public_inputs;
223 ASSERT_EQ(num_public_inputs, HidingKernelIOSerde::PUBLIC_INPUTS_SIZE)
224 << "Tail kernel should use HidingKernelIO format";
225 tail_io = HidingKernelIOSerde::from_proof(it.proof, num_public_inputs);
226 break;
227 }
228 }
229
230 // Generate the final proof (creates HidingKernel)
231 auto proof = ivc.prove();
232 auto vk_and_hash = ivc.get_hiding_kernel_vk_and_hash();
233
234 // Extract field from HidingKernel's proof (final mega_proof)
235 size_t hiding_kernel_pub_inputs = vk_and_hash->vk->num_public_inputs;
236 ASSERT_EQ(hiding_kernel_pub_inputs, HidingKernelIOSerde::PUBLIC_INPUTS_SIZE)
237 << "HidingKernel should use HidingKernelIO format";
238 HidingKernelIOSerde hiding_io = HidingKernelIOSerde::from_proof(proof.mega_proof, hiding_kernel_pub_inputs);
239
240 // Verify field propagated correctly from Tail kernel to HidingKernel
241 switch (field_to_test) {
243 EXPECT_EQ(tail_io.pairing_inputs.P0, hiding_io.pairing_inputs.P0)
244 << "P0 mismatch: Tail has " << tail_io.pairing_inputs.P0 << " but HidingKernel has "
245 << hiding_io.pairing_inputs.P0;
246 EXPECT_EQ(tail_io.pairing_inputs.P1, hiding_io.pairing_inputs.P1)
247 << "P1 mismatch: Tail has " << tail_io.pairing_inputs.P1 << " but HidingKernel has "
248 << hiding_io.pairing_inputs.P1;
249 break;
251 EXPECT_EQ(tail_io.kernel_return_data, hiding_io.kernel_return_data)
252 << "kernel_return_data mismatch: Tail has " << tail_io.kernel_return_data << " but HidingKernel has "
253 << hiding_io.kernel_return_data;
254 break;
256 for (size_t i = 0; i < tail_io.ecc_op_tables.size(); ++i) {
257 EXPECT_EQ(tail_io.ecc_op_tables[i], hiding_io.ecc_op_tables[i])
258 << "M_tail[" << i << "] mismatch: Tail has " << tail_io.ecc_op_tables[i] << " but HidingKernel has "
259 << hiding_io.ecc_op_tables[i];
260 }
261 break;
262 }
263 }
264};
265
273TEST_F(ChonkTests, TestCircuitSizes)
274{
275 const size_t NUM_APP_CIRCUITS = 2;
276
277 // Check circuit sizes when no settings are passed
278 {
279 auto [proof, vk] = accumulate_and_prove_ivc(NUM_APP_CIRCUITS, {}, true);
280 EXPECT_TRUE(verify_chonk(proof, vk));
281 }
282
283 // Check circuit sizes when no settings are passed
284 {
285 auto [proof, vk] =
286 accumulate_and_prove_ivc(NUM_APP_CIRCUITS, { .log2_num_gates = SMALL_LOG_2_NUM_GATES }, true);
287 EXPECT_TRUE(verify_chonk(proof, vk));
288 }
289};
290
298{
299 const size_t NUM_APP_CIRCUITS = 2;
300 auto [proof, vk] = accumulate_and_prove_ivc(NUM_APP_CIRCUITS);
301
302 EXPECT_TRUE(verify_chonk(proof, vk));
303};
304
312TEST_F(ChonkTests, BadProofFailure)
313{
314 BB_DISABLE_ASSERTS(); // Disable assert in HN prover
315
316 const size_t NUM_APP_CIRCUITS = 2;
317 // Confirm that the IVC verifies if nothing is tampered with
318 {
319
320 CircuitProducer circuit_producer(NUM_APP_CIRCUITS);
321 const size_t NUM_CIRCUITS = circuit_producer.total_num_circuits;
322 Chonk ivc{ NUM_CIRCUITS };
323 TestSettings settings{ .log2_num_gates = SMALL_LOG_2_NUM_GATES };
324
325 // Construct and accumulate a set of mocked private function execution circuits
326 for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) {
327 circuit_producer.construct_and_accumulate_next_circuit(ivc, settings);
328 }
329 auto proof = ivc.prove();
330 EXPECT_TRUE(verify_chonk(proof, ivc.get_hiding_kernel_vk_and_hash()));
331 }
332
333 // The IVC throws an exception if the FIRST fold proof is tampered with
334 {
335 CircuitProducer circuit_producer(NUM_APP_CIRCUITS);
336 const size_t NUM_CIRCUITS = circuit_producer.total_num_circuits;
337 Chonk ivc{ NUM_CIRCUITS };
338
339 size_t num_public_inputs = 0;
340
341 // Construct and accumulate a set of mocked private function execution circuits
342 for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) {
343 auto [circuit, vk] =
344 circuit_producer.create_next_circuit_and_vk(ivc, { .log2_num_gates = SMALL_LOG_2_NUM_GATES });
345 ivc.accumulate(circuit, vk);
346
347 if (idx == 1) {
348 num_public_inputs = circuit.num_public_inputs();
349 }
350
351 if (idx == 2) {
352 EXPECT_EQ(ivc.verification_queue.size(), 2); // two proofs after 3 calls to accumulation
353 tamper_with_proof(ivc.verification_queue[0].proof,
354 num_public_inputs); // tamper with first proof
355 }
356 }
357 auto proof = ivc.prove();
358 EXPECT_FALSE(verify_chonk(proof, ivc.get_hiding_kernel_vk_and_hash()));
359 }
360
361 // The IVC fails if the SECOND fold proof is tampered with
362 {
363 CircuitProducer circuit_producer(NUM_APP_CIRCUITS);
364 const size_t NUM_CIRCUITS = circuit_producer.total_num_circuits;
365 Chonk ivc{ NUM_CIRCUITS };
366
367 // Construct and accumulate a set of mocked private function execution circuits
368 for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) {
369 auto [circuit, vk] =
370 circuit_producer.create_next_circuit_and_vk(ivc, { .log2_num_gates = SMALL_LOG_2_NUM_GATES });
371 ivc.accumulate(circuit, vk);
372
373 if (idx == 2) {
374 EXPECT_EQ(ivc.verification_queue.size(), 2); // two proofs after 3 calls to accumulation
375 tamper_with_proof(ivc.verification_queue[1].proof,
376 circuit.num_public_inputs()); // tamper with second proof
377 }
378 }
379 auto proof = ivc.prove();
380 EXPECT_FALSE(verify_chonk(proof, ivc.get_hiding_kernel_vk_and_hash()));
381 }
382};
383
388TEST_F(ChonkTests, VKIndependenceFromNumberOfCircuits)
389{
390 const TestSettings settings{ .log2_num_gates = SMALL_LOG_2_NUM_GATES };
391
392 auto [unused_1, vk_and_hash_1] = accumulate_and_prove_ivc(/*num_app_circuits=*/1, settings);
393 auto [unused_2, vk_and_hash_2] = accumulate_and_prove_ivc(/*num_app_circuits=*/3, settings);
394
395 // Check the equality of the hiding kernel VKeys
396 EXPECT_EQ(*vk_and_hash_1->vk.get(), *vk_and_hash_2->vk.get());
397};
398
403TEST_F(ChonkTests, VKIndependenceFromCircuitSize)
404{
405 // Run IVC for two sets of circuits
406 const size_t NUM_APP_CIRCUITS = 1;
407 const size_t log2_num_gates_small = 5;
408 const size_t log2_num_gates_big = 18;
409
410 const TestSettings settings_1{ .log2_num_gates = log2_num_gates_small };
411 const TestSettings settings_2{ .log2_num_gates = log2_num_gates_big };
412
413 auto [unused_1, vk_and_hash_1] = accumulate_and_prove_ivc(NUM_APP_CIRCUITS, settings_1);
414 auto [unused_2, vk_and_hash_2] = accumulate_and_prove_ivc(NUM_APP_CIRCUITS, settings_2);
415
416 // Check the equality of the hiding kernel VKeys
417 EXPECT_EQ(*vk_and_hash_1->vk.get(), *vk_and_hash_2->vk.get());
418};
419
424HEAVY_TEST(ChonkKernelCapacity, MaxCapacityPassing)
425{
427
428 const size_t NUM_APP_CIRCUITS = 17;
429 auto [proof, vk] = ChonkTests::accumulate_and_prove_ivc(NUM_APP_CIRCUITS);
430
431 bool verified = ChonkTests::verify_chonk(proof, vk);
432 EXPECT_TRUE(verified);
433};
434
439TEST_F(ChonkTests, MsgpackProofFromFileOrBuffer)
440{
441 // Generate an arbitrary valid CICV proof
442 TestSettings settings{ .log2_num_gates = SMALL_LOG_2_NUM_GATES };
443 auto [proof, vk] = accumulate_and_prove_ivc(/*num_app_circuits=*/1, settings);
444
445 { // Serialize/deserialize the proof to/from a file, check that it verifies
446 const std::string filename = "proof.msgpack";
447 proof.to_file_msgpack(filename);
448 auto proof_deserialized = ChonkProof::from_file_msgpack(filename);
449
450 EXPECT_TRUE(verify_chonk(proof_deserialized, vk));
451 }
452
453 { // Serialize/deserialize proof to/from a heap buffer, check that it verifies
454 uint8_t* buffer = proof.to_msgpack_heap_buffer();
455 auto uint8_buffer = from_buffer<std::vector<uint8_t>>(buffer);
456 uint8_t const* uint8_ptr = uint8_buffer.data();
457 auto proof_deserialized = ChonkProof::from_msgpack_buffer(uint8_ptr);
458
459 EXPECT_TRUE(verify_chonk(proof_deserialized, vk));
460 }
461
462 { // Check that attempting to deserialize a proof from a buffer with random bytes fails gracefully
463 msgpack::sbuffer buffer = proof.to_msgpack_buffer();
464 auto proof_deserialized = ChonkProof::from_msgpack_buffer(buffer);
465 EXPECT_TRUE(verify_chonk(proof_deserialized, vk));
466
467 std::vector<uint8_t> random_bytes(buffer.size());
468 std::generate(random_bytes.begin(), random_bytes.end(), []() { return static_cast<uint8_t>(rand() % 256); });
469 std::copy(random_bytes.begin(), random_bytes.end(), buffer.data());
470
471 // Expect deserialization to fail with error msgpack::v1::type_error with description "std::bad_cast"
472 EXPECT_THROW(ChonkProof::from_msgpack_buffer(buffer), msgpack::v1::type_error);
473 }
474};
475
481TEST_F(ChonkTests, KernelPairingInputsTamperingFailure)
482{
483 ChonkTests::test_kernel_io_tampering(KernelIOField::PAIRING_INPUTS);
484}
485
491TEST_F(ChonkTests, AppPairingInputsTamperingFailure)
492{
494}
495
502TEST_F(ChonkTests, AccumulatorHashTamperingFailure)
503{
504 ChonkTests::test_kernel_io_tampering(KernelIOField::ACCUMULATOR_HASH);
505}
506
512TEST_F(ChonkTests, KernelReturnDataTamperingFailure)
513{
514 ChonkTests::test_kernel_io_tampering(KernelIOField::KERNEL_RETURN_DATA);
515}
516
522TEST_F(ChonkTests, AppReturnDataTamperingFailure)
523{
524 ChonkTests::test_kernel_io_tampering(KernelIOField::APP_RETURN_DATA);
525}
526
532TEST_F(ChonkTests, EccOpTablesTamperingFailure)
533{
534 ChonkTests::test_kernel_io_tampering(KernelIOField::ECC_OP_TABLES);
535}
536
543TEST_F(ChonkTests, PairingPointsPropagationConsistency)
544{
545 ChonkTests::test_hiding_kernel_io_propagation(HidingKernelIOField::PAIRING_INPUTS);
546}
547
553TEST_F(ChonkTests, KernelReturnDataPropagationConsistency)
554{
555 ChonkTests::test_hiding_kernel_io_propagation(HidingKernelIOField::KERNEL_RETURN_DATA);
556}
557
563TEST_F(ChonkTests, MTailPropagationConsistency)
564{
565 ChonkTests::test_hiding_kernel_io_propagation(HidingKernelIOField::ECC_OP_TABLES);
566}
#define BB_DISABLE_ASSERTS()
Definition assert.hpp:33
TEST_F(ChonkTests, TestCircuitSizes)
Test sizes of the circuits generated by MockCircuitProducer.
PrivateFunctionExecutionMockCircuitProducer CircuitProducer
static void test_hiding_kernel_io_propagation(HidingKernelIOField field_to_test)
Helper function to test HidingKernelIO field propagation consistency.
static bool verify_chonk(const ChonkProof &proof, const std::shared_ptr< MegaZKFlavor::VKAndHash > &vk_and_hash)
static void tamper_with_proof(HonkProof &proof, size_t public_inputs_offset)
Tamper with a proof.
HidingKernelIOField
Enum for specifying which HidingKernelIO field to test for propagation consistency.
Flavor::Commitment Commitment
static void test_kernel_io_tampering(KernelIOField field_to_tamper)
Helper function to test tampering with KernelIO fields.
static void SetUpTestSuite()
typename Flavor::FF FF
static std::pair< ChonkProof, std::shared_ptr< MegaZKFlavor::VKAndHash > > accumulate_and_prove_ivc(size_t num_app_circuits, TestSettings settings={}, bool check_circuit_sizes=false)
static void test_app_io_tampering()
Helper function to test tampering with AppIO pairing inputs.
KernelIOField
Enum for specifying which KernelIO field to tamper with in tests.
The IVC scheme used by the aztec client for private function execution.
Definition chonk.hpp:38
HypernovaDeciderProver DeciderProver
Definition chonk.hpp:77
ChonkProof prove()
Construct Chonk proof, which, if verified, fully establishes the correctness of RCG.
Definition chonk.cpp:547
ProverInstance_< Flavor > ProverInstance
Definition chonk.hpp:49
MegaFlavor Flavor
Definition chonk.hpp:42
VerifierInstance_< Flavor > VerifierInstance
Definition chonk.hpp:51
void accumulate(ClientCircuit &circuit, const std::shared_ptr< MegaVerificationKey > &precomputed_vk) override
Perform prover work for accumulation (e.g. HN folding, merge proving)
Definition chonk.cpp:397
MegaCircuitBuilder ClientCircuit
Definition chonk.hpp:52
Verifier for Chonk IVC proofs (both native and recursive).
Output verify(const Proof &proof)
Verify a Chonk proof.
HyperNova decider prover. Produces final opening proof for the accumulated claim.
Curve::ScalarField FF
Curve::AffineElement Commitment
NativeVerificationKey_< PrecomputedEntities< Commitment >, Codec, HashFunction, CommitmentKey > VerificationKey
The verification key is responsible for storing the commitments to the precomputed (non-witness) poly...
Base Native verification key class.
Definition flavor.hpp:141
A ProverInstance is normally constructed from a finalized circuit and it contains all the information...
The VerifierInstance encapsulates all the necessary information for a Honk Verifier to verify a proof...
Native representation and serde for AppIO public inputs.
Native representation and serde for HidingKernelIO public inputs.
For test purposes only: Native representation and serde for KernelIO public inputs
uint8_t buffer[RANDOM_BUFFER_SIZE]
Definition engine.cpp:34
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
::testing::Types< BN254Settings, GrumpkinSettings > TestSettings
void tamper_with_proof(InnerProver &inner_prover, ProofType &inner_proof, TamperType type)
Test method that provides several ways to tamper with a proof. TODO(https://github....
ChonkVerifier< false > ChonkNativeVerifier
VerifierCommitmentKey< Curve > vk
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
static ChonkProof_ from_msgpack_buffer(uint8_t const *&buffer)
static ChonkProof_ from_file_msgpack(const std::string &filename)
#define HEAVY_TEST(x, y)
Definition test.hpp:9