Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
client_ivc.test.cpp
Go to the documentation of this file.
14#include "gtest/gtest.h"
15
16using namespace bb;
17
18static constexpr size_t SMALL_LOG_2_NUM_GATES = 5;
19// TODO(https://github.com/AztecProtocol/barretenberg/issues/1511): The CIVC class should enforce the minimum number of
20// circuits in a test flow.
21
22class ClientIVCTests : public ::testing::Test {
23 protected:
25
27 using FF = typename Flavor::FF;
40 using CircuitProducer = PrivateFunctionExecutionMockCircuitProducer;
41
42 public:
49 static void tamper_with_proof(FoldProof& proof, size_t public_inputs_offset)
50 {
51 // Tamper with the commitment in the proof
52 Commitment commitment = bb::field_conversion::convert_from_bn254_frs<Commitment>(
53 std::span{ proof }.subspan(public_inputs_offset, bb::field_conversion::calc_num_bn254_frs<Commitment>()));
54 commitment = commitment + Commitment::one();
55 auto commitment_frs = bb::field_conversion::convert_to_bn254_frs<Commitment>(commitment);
56 for (size_t idx = 0; idx < 4; ++idx) {
57 proof[public_inputs_offset + idx] = commitment_frs[idx];
58 }
59 }
60
62 TestSettings settings = {})
63 {
64 CircuitProducer circuit_producer(num_app_circuits);
65 const size_t num_circuits = circuit_producer.total_num_circuits;
66 TraceSettings trace_settings{ AZTEC_TRACE_STRUCTURE };
67 ClientIVC ivc{ num_circuits, trace_settings };
68
69 for (size_t j = 0; j < num_circuits; ++j) {
70 // Use default test settings for the mock hiding kernel since it's size must always be consistent
71 if (j == num_circuits - 1) {
72 settings = TestSettings{};
73 }
74 circuit_producer.construct_and_accumulate_next_circuit(ivc, settings);
75 }
76 return { ivc.prove(), ivc.get_vk() };
77 };
78};
79
84TEST_F(ClientIVCTests, BasicStructured)
85{
86 const size_t NUM_APP_CIRCUITS = 1;
87 auto [proof, vk] = accumulate_and_prove_ivc(NUM_APP_CIRCUITS);
88
89 EXPECT_TRUE(ClientIVC::verify(proof, vk));
90};
91
99TEST_F(ClientIVCTests, BadProofFailure)
100{
101 const size_t NUM_APP_CIRCUITS = 2;
102 TraceSettings trace_settings{ SMALL_TEST_STRUCTURE };
103 // Confirm that the IVC verifies if nothing is tampered with
104 {
105
106 CircuitProducer circuit_producer(NUM_APP_CIRCUITS);
107 const size_t NUM_CIRCUITS = circuit_producer.total_num_circuits;
108 ClientIVC ivc{ NUM_CIRCUITS, trace_settings };
109 TestSettings settings{ .log2_num_gates = SMALL_LOG_2_NUM_GATES };
110
111 // Construct and accumulate a set of mocked private function execution circuits
112 for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) {
113 circuit_producer.construct_and_accumulate_next_circuit(ivc, settings);
114 }
115 EXPECT_TRUE(ivc.prove_and_verify());
116 }
117
118 // The IVC throws an exception if the FIRST fold proof is tampered with
119 {
120 CircuitProducer circuit_producer(NUM_APP_CIRCUITS);
121 const size_t NUM_CIRCUITS = circuit_producer.total_num_circuits;
122 ClientIVC ivc{ NUM_CIRCUITS, trace_settings };
123
124 size_t num_public_inputs = 0;
125
126 // Construct and accumulate a set of mocked private function execution circuits
127 for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) {
128 auto [circuit, vk] =
129 circuit_producer.create_next_circuit_and_vk(ivc, { .log2_num_gates = SMALL_LOG_2_NUM_GATES });
130 ivc.accumulate(circuit, vk);
131
132 if (idx == 1) {
133 num_public_inputs = circuit.num_public_inputs();
134 }
135
136 if (idx == 2) {
137 EXPECT_EQ(ivc.verification_queue.size(), 2); // two proofs after 3 calls to accumulation
138 tamper_with_proof(ivc.verification_queue[0].proof,
139 num_public_inputs); // tamper with first proof
140 }
141 }
142 EXPECT_FALSE(ivc.prove_and_verify());
143 }
144
145 // The IVC fails if the SECOND fold proof is tampered with
146 {
147 CircuitProducer circuit_producer(NUM_APP_CIRCUITS);
148 const size_t NUM_CIRCUITS = circuit_producer.total_num_circuits;
149 ClientIVC ivc{ NUM_CIRCUITS, trace_settings };
150
151 // Construct and accumulate a set of mocked private function execution circuits
152 for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) {
153 auto [circuit, vk] =
154 circuit_producer.create_next_circuit_and_vk(ivc, { .log2_num_gates = SMALL_LOG_2_NUM_GATES });
155 ivc.accumulate(circuit, vk);
156
157 if (idx == 2) {
158 EXPECT_EQ(ivc.verification_queue.size(), 2); // two proofs after 3 calls to accumulation
159 tamper_with_proof(ivc.verification_queue[1].proof,
160 circuit.num_public_inputs()); // tamper with second proof
161 }
162 }
163 EXPECT_FALSE(ivc.prove_and_verify());
164 }
165
166 EXPECT_TRUE(true);
167};
168
174TEST_F(ClientIVCTests, WrongProofComponentFailure)
175{
176 // Produce two valid proofs
177 auto [civc_proof_1, civc_vk_1] = accumulate_and_prove_ivc(/*num_app_circuits=*/1);
178 {
179 EXPECT_TRUE(ClientIVC::verify(civc_proof_1, civc_vk_1));
180 }
181
182 auto [civc_proof_2, civc_vk_2] = accumulate_and_prove_ivc(/*num_app_circuits=*/1);
183 {
184 EXPECT_TRUE(ClientIVC::verify(civc_proof_2, civc_vk_2));
185 }
186
187 {
188 // Replace Merge proof
189 ClientIVC::Proof tampered_proof = civc_proof_1;
190
191 tampered_proof.goblin_proof.merge_proof = civc_proof_2.goblin_proof.merge_proof;
192
193 EXPECT_THROW_OR_ABORT(ClientIVC::verify(tampered_proof, civc_vk_1), ".*IPA verification fails.*");
194 }
195
196 {
197 // Replace hiding circuit proof
198 ClientIVC::Proof tampered_proof = civc_proof_1;
199
200 tampered_proof.mega_proof = civc_proof_2.mega_proof;
201
202 EXPECT_THROW_OR_ABORT(ClientIVC::verify(tampered_proof, civc_vk_1), ".*IPA verification fails.*");
203 }
204
205 {
206 // Replace ECCVM proof
207 ClientIVC::Proof tampered_proof = civc_proof_1;
208
209 tampered_proof.goblin_proof.eccvm_proof = civc_proof_2.goblin_proof.eccvm_proof;
210
211 EXPECT_THROW_OR_ABORT(ClientIVC::verify(tampered_proof, civc_vk_1), ".*IPA verification fails.*");
212 }
213
214 {
215 // Replace Translator proof
216 ClientIVC::Proof tampered_proof = civc_proof_1;
217
218 tampered_proof.goblin_proof.translator_proof = civc_proof_2.goblin_proof.translator_proof;
219
220 EXPECT_FALSE(ClientIVC::verify(tampered_proof, civc_vk_1));
221 }
222};
223
228TEST_F(ClientIVCTests, VKIndependenceTest)
229{
230 const TestSettings settings{ .log2_num_gates = SMALL_LOG_2_NUM_GATES };
231
232 auto [unused_1, civc_vk_1] = accumulate_and_prove_ivc(/*num_app_circuits=*/1, settings);
233 auto [unused_2, civc_vk_2] = accumulate_and_prove_ivc(/*num_app_circuits=*/3, settings);
234
235 // Check the equality of the Mega components of the ClientIVC VKeys.
236 EXPECT_EQ(*civc_vk_1.mega.get(), *civc_vk_2.mega.get());
237
238 // Check the equality of the ECCVM components of the ClientIVC VKeys.
239 EXPECT_EQ(*civc_vk_1.eccvm.get(), *civc_vk_2.eccvm.get());
240
241 // Check the equality of the Translator components of the ClientIVC VKeys.
242 EXPECT_EQ(*civc_vk_1.translator.get(), *civc_vk_2.translator.get());
243};
244
253TEST_F(ClientIVCTests, VKIndependenceWithOverflow)
254{
255 // Run IVC for two sets of circuits: a nomical case where all circuits fit within the structured trace and an
256 // "overflow" case where all (but importantly at least one) circuit overflows the structured trace.
257 const size_t NUM_APP_CIRCUITS = 1;
258 const size_t log2_num_gates_nominal = 5; // number of gates in baseline mocked circuits
259 const size_t log2_num_gates_overflow = 18; // number of gates in the "overflow" mocked circuit
260
261 const TestSettings settings_1{ .log2_num_gates = log2_num_gates_nominal };
262 const TestSettings settings_2{ .log2_num_gates = log2_num_gates_overflow };
263
264 auto [unused_1, civc_vk_1] = accumulate_and_prove_ivc(NUM_APP_CIRCUITS, settings_1);
265 auto [unused_2, civc_vk_2] = accumulate_and_prove_ivc(NUM_APP_CIRCUITS, settings_2);
266
267 // Check the equality of the Mega components of the ClientIVC VKeys.
268 EXPECT_EQ(*civc_vk_1.mega.get(), *civc_vk_2.mega.get());
269
270 // Check the equality of the ECCVM components of the ClientIVC VKeys.
271 EXPECT_EQ(*civc_vk_1.eccvm.get(), *civc_vk_2.eccvm.get());
272
273 // Check the equality of the Translator components of the ClientIVC VKeys.
274 EXPECT_EQ(*civc_vk_1.translator.get(), *civc_vk_2.translator.get());
275};
276
281HEAVY_TEST(ClientIVCKernelCapacity, MaxCapacityPassing)
282{
284
285 const size_t NUM_APP_CIRCUITS = 14;
286 auto [proof, vk] = ClientIVCTests::accumulate_and_prove_ivc(NUM_APP_CIRCUITS);
287
288 bool verified = ClientIVC::verify(proof, vk);
289 EXPECT_TRUE(verified);
290};
291
298TEST_F(ClientIVCTests, StructuredTraceOverflow)
299{
300
301 // Define trace settings with sufficient overflow capacity to accommodate each of the circuits to be accumulated
302 size_t NUM_APP_CIRCUITS = 1;
303 CircuitProducer circuit_producer(NUM_APP_CIRCUITS);
304 size_t NUM_CIRCUITS = circuit_producer.total_num_circuits;
305 ClientIVC ivc{ NUM_CIRCUITS, { SMALL_TEST_STRUCTURE, /*overflow_capacity=*/1 << 17 } };
306 TestSettings settings;
307
308 // Construct and accumulate some circuits of varying size
309 size_t log2_num_gates = 14;
310 for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) {
311 settings.log2_num_gates = log2_num_gates;
312 circuit_producer.construct_and_accumulate_next_circuit(ivc, settings);
313 log2_num_gates += 1;
314 }
315
316 EXPECT_TRUE(ivc.prove_and_verify());
317};
318
323TEST_F(ClientIVCTests, DynamicTraceOverflow)
324{
325 struct TestCase {
326 std::string name;
327 std::vector<size_t> log2_num_arith_gates;
328 };
329
330 // Define some test cases that overflow the structured trace in different ways at different points in the
331 // accumulation. We distinguish between a simple overflow that exceeds one or more structured trace capacities but
332 // does not bump the dyadic circuit size and an overflow that does increase the dyadic circuit size.
333 std::vector<TestCase> test_cases = {
334 { "Case 1", { 14, 18, 14, 16, 14 } }, /* dyadic size overflow then simple overflow */
335 { "Case 2", { 14, 16, 14, 18, 14 } }, /* simple overflow then dyadic size overflow */
336 };
337
338 for (const auto& test : test_cases) {
339 SCOPED_TRACE(test.name); // improves test output readability
340
341 CircuitProducer circuit_producer(/*num_app_circuits=*/1);
342 const size_t NUM_CIRCUITS = circuit_producer.total_num_circuits;
343 ClientIVC ivc{ NUM_CIRCUITS, { SMALL_TEST_STRUCTURE_FOR_OVERFLOWS } };
344
345 // Accumulate
346 for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) {
347 circuit_producer.construct_and_accumulate_next_circuit(
348 ivc, { .log2_num_gates = test.log2_num_arith_gates[idx] });
349 }
350
351 EXPECT_EQ(check_accumulator_target_sum_manual(ivc.fold_output.accumulator), true);
352 EXPECT_TRUE(ivc.prove_and_verify());
353 }
354}
355
360TEST_F(ClientIVCTests, MsgpackProofFromFileOrBuffer)
361{
362 // Generate an arbitrary valid CICV proof
363 TestSettings settings{ .log2_num_gates = SMALL_LOG_2_NUM_GATES };
364 auto [proof, vk] = accumulate_and_prove_ivc(/*num_app_circuits=*/1, settings);
365
366 { // Serialize/deserialize the proof to/from a file, check that it verifies
367 const std::string filename = "proof.msgpack";
368 proof.to_file_msgpack(filename);
369 auto proof_deserialized = ClientIVC::Proof::from_file_msgpack(filename);
370
371 EXPECT_TRUE(ClientIVC::verify(proof_deserialized, vk));
372 }
373
374 { // Serialize/deserialize proof to/from a heap buffer, check that it verifies
375 uint8_t* buffer = proof.to_msgpack_heap_buffer();
376 auto uint8_buffer = from_buffer<std::vector<uint8_t>>(buffer);
377 uint8_t const* uint8_ptr = uint8_buffer.data();
378 auto proof_deserialized = ClientIVC::Proof::from_msgpack_buffer(uint8_ptr);
379
380 EXPECT_TRUE(ClientIVC::verify(proof_deserialized, vk));
381 }
382
383 { // Check that attempting to deserialize a proof from a buffer with random bytes fails gracefully
384 msgpack::sbuffer buffer = proof.to_msgpack_buffer();
385 auto proof_deserialized = ClientIVC::Proof::from_msgpack_buffer(buffer);
386 EXPECT_TRUE(ClientIVC::verify(proof_deserialized, vk));
387
388 std::vector<uint8_t> random_bytes(buffer.size());
389 std::generate(random_bytes.begin(), random_bytes.end(), []() { return static_cast<uint8_t>(rand() % 256); });
390 std::copy(random_bytes.begin(), random_bytes.end(), buffer.data());
391
392 // Expect deserialization to fail with error msgpack::v1::type_error with description "std::bad_cast"
393 EXPECT_THROW(ClientIVC::Proof::from_msgpack_buffer(buffer), msgpack::v1::type_error);
394 }
395};
396
406TEST_F(ClientIVCTests, DatabusFailure)
407{
408 PrivateFunctionExecutionMockCircuitProducer circuit_producer{ /*num_app_circuits=*/1 };
409 const size_t NUM_CIRCUITS = circuit_producer.total_num_circuits;
410 ClientIVC ivc{ NUM_CIRCUITS, { AZTEC_TRACE_STRUCTURE } };
411
412 // Construct and accumulate a series of mocked private function execution circuits
413 for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) {
414 auto [circuit, vk] = circuit_producer.create_next_circuit_and_vk(ivc);
415
416 // Tamper with the return data of the app circuit before it is processed as input to the next kernel
417 if (idx == 0) {
418 circuit_producer.tamper_with_databus();
419 }
420
421 ivc.accumulate(circuit, vk);
422 }
423
424 EXPECT_FALSE(ivc.prove_and_verify());
425};
#define EXPECT_THROW_OR_ABORT(statement, matcher)
Definition assert.hpp:150
static std::pair< ClientIVC::Proof, ClientIVC::VerificationKey > accumulate_and_prove_ivc(size_t num_app_circuits, TestSettings settings={})
static void SetUpTestSuite()
PrivateFunctionExecutionMockCircuitProducer CircuitProducer
static void tamper_with_proof(FoldProof &proof, size_t public_inputs_offset)
Tamper with a proof.
typename Flavor::FF FF
Flavor::Commitment Commitment
ClientIVC::FoldProof FoldProof
The IVC scheme used by the aztec client for private function execution.
Proof prove()
Construct a proof for the IVC, which, if verified, fully establishes its correctness.
DeciderProvingKey_< Flavor > DeciderProvingKey
MegaFlavor Flavor
void accumulate(ClientCircuit &circuit, const std::shared_ptr< MegaVerificationKey > &precomputed_vk)
Perform prover work for accumulation (e.g. PG folding, merge proving)
DeciderVerifier_< Flavor > DeciderVerifier
DeciderProver_< Flavor > DeciderProver
std::vector< FF > FoldProof
MegaCircuitBuilder ClientCircuit
DeciderVerificationKey_< Flavor > DeciderVerificationKey
static bool verify(const Proof &proof, const VerificationKey &vk)
A DeciderProvingKey is normally constructed from a finalized circuit and it contains all the informat...
The DeciderVerificationKey encapsulates all the necessary information for a Mega Honk Verifier to ver...
The verification key is responsible for storing the commitments to the precomputed (non-witness) poly...
Curve::ScalarField FF
Curve::AffineElement Commitment
TEST_F(ClientIVCTests, BasicStructured)
Using a structured trace allows for the accumulation of circuits of varying size.
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.
::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....
VerifierCommitmentKey< Curve > vk
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
A full proof for the IVC scheme containing a Mega proof showing correctness of the hiding circuit (wh...
static Proof from_msgpack_buffer(uint8_t const *&buffer)
static Proof from_file_msgpack(const std::string &filename)
GoblinProof goblin_proof
ECCVMProof eccvm_proof
Definition types.hpp:22
HonkProof merge_proof
Definition types.hpp:21
HonkProof translator_proof
Definition types.hpp:23
#define HEAVY_TEST(x, y)
Definition test.hpp:9