Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
api_client_ivc.test.cpp
Go to the documentation of this file.
1#include "api_client_ivc.hpp"
19#include <chrono>
20#include <cstddef>
21#include <cstdlib>
22#include <filesystem>
23#include <gtest/gtest.h>
24#include <sstream>
25#include <string_view>
26
27using namespace bb;
28
29namespace {
30// Create a unique temporary directory for each test run
31// Uniqueness needed because tests are run in parallel and write to same file names.
32std::filesystem::path get_test_dir(const std::string_view& test_name)
33{
34 std::filesystem::path temp_dir = "tmp_api_client_ivc_test";
35 std::filesystem::create_directories(temp_dir);
36 std::filesystem::create_directories(temp_dir / test_name);
37 return temp_dir / test_name;
38}
39
40// TODO(https://github.com/AztecProtocol/barretenberg/issues/1509): expand these test to accomodate a more realistic
41// CIVC flow in order to re-enable the ProveAndVerify* tests in this file
42void create_test_private_execution_steps(const std::filesystem::path& output_path)
43{
44 using namespace acir_format;
45
46 // First create a simple app circuit
47 auto [app_bytecode, app_witness_data] = acir_bincode_mocks::create_simple_circuit_bytecode();
48
49 // Get the VK for the app circuit
50 bbapi::BBApiRequest request;
51
52 auto app_vk_response =
53 bbapi::ClientIvcComputeStandaloneVk{ .circuit = { .name = "app_circuit", .bytecode = app_bytecode } }.execute();
54
55 // Decode VK to get field elements
56 auto app_vk = from_buffer<MegaFlavor::VerificationKey>(app_vk_response.bytes);
57 auto app_vk_fields = app_vk.to_field_elements();
58
59 // Now create a kernel circuit that verifies the app circuit
60 auto kernel_bytecode = acir_bincode_mocks::create_simple_kernel(app_vk_fields.size(), /*is_init_kernel=*/true);
61 auto kernel_witness_data = acir_bincode_mocks::create_kernel_witness(app_vk_fields);
62
63 auto kernel_vk_response = bbapi::ClientIvcComputeStandaloneVk{
64 .circuit = { .name = "kernel_circuit", .bytecode = kernel_bytecode }
65 }.execute();
66 auto kernel_vk = kernel_vk_response.bytes;
67
68 // Create PrivateExecutionStepRaw for the kernel
70 raw_steps.push_back({ .bytecode = app_bytecode,
71 .witness = app_witness_data,
72 .vk = app_vk_response.bytes,
73 .function_name = "app_function" });
74 raw_steps.push_back({ .bytecode = kernel_bytecode,
75 .witness = kernel_witness_data,
76 .vk = kernel_vk,
77 .function_name = "kernel_function" });
78
80}
81} // namespace
82
83class ClientIVCAPITests : public ::testing::Test {
84 protected:
86
87 void SetUp() override
88 {
89 const auto* info = ::testing::UnitTest::GetInstance()->current_test_info();
90 test_dir = get_test_dir(info->name());
91 }
92
93 void TearDown() override
94 {
95 if (std::filesystem::exists(test_dir)) {
96 std::filesystem::remove_all(test_dir);
97 }
98 }
99
100 std::filesystem::path test_dir;
101};
102
103namespace bb {
104std::vector<uint8_t> compress(const std::vector<uint8_t>& input);
105} // namespace bb
106
107// Helper to get an IVC verification key for testing
108ClientIVC::MegaVerificationKey get_ivc_vk(const std::filesystem::path& test_dir)
109{
110 auto [app_bytecode, app_witness_data] = acir_bincode_mocks::create_simple_circuit_bytecode();
111 bbapi::BBApiRequest request;
112 auto app_vk_response =
113 bbapi::ClientIvcComputeStandaloneVk{ .circuit = { .name = "app_circuit", .bytecode = app_bytecode } }.execute();
114
115 // Decode to get the field count
116 auto app_vk = from_buffer<MegaFlavor::VerificationKey>(app_vk_response.bytes);
117 size_t vk_field_count = app_vk.to_field_elements().size();
118
119 // Create a kernel circuit with the correct VK size
120 auto bytecode = acir_bincode_mocks::create_simple_kernel(vk_field_count, /*is_init_kernel=*/false);
121 std::filesystem::path bytecode_path = test_dir / "circuit.acir";
122 write_file(bytecode_path, bb::compress(bytecode));
123
124 ClientIVCAPI::Flags write_vk_flags;
125 write_vk_flags.verifier_type = "ivc";
126
127 ClientIVCAPI api;
128 api.write_vk(write_vk_flags, bytecode_path, test_dir);
129
130 auto buffer = read_file(test_dir / "vk");
131 return from_buffer<ClientIVC::MegaVerificationKey>(buffer);
132};
133
134// Test the ClientIVCAPI::prove flow, making sure --write_vk
135// returns the same output as our ivc VK generation.
136TEST_F(ClientIVCAPITests, DISABLED_ProveAndVerifyFileBasedFlow)
137{
138 auto ivc_vk = get_ivc_vk(test_dir);
139
140 // Create test input file
141 std::filesystem::path input_path = test_dir / "input.msgpack";
142 create_test_private_execution_steps(input_path);
143
144 std::filesystem::path output_dir = test_dir / "output";
145 std::filesystem::create_directories(output_dir);
146
147 // Helper lambda to create proof and VK files
148 auto create_proof_and_vk = [&]() {
150 flags.write_vk = true;
151 ClientIVCAPI api;
152 api.prove(flags, input_path, output_dir);
153 };
154
155 // Helper lambda to verify VK equivalence
156 auto verify_vk_equivalence = [&](const std::filesystem::path& vk1_path, const ClientIVC::MegaVerificationKey& vk2) {
157 auto vk1_data = read_file(vk1_path);
158 auto vk1 = from_buffer<ClientIVC::MegaVerificationKey>(vk1_data);
159 ASSERT_EQ(vk1, vk2);
160 };
161
162 // Helper lambda to verify proof
163 auto verify_proof = [&]() {
164 std::filesystem::path proof_path = output_dir / "proof";
165 std::filesystem::path vk_path = output_dir / "vk";
166 std::filesystem::path public_inputs_path; // Not used for ClientIVC
167
169 ClientIVCAPI verify_api;
170 return verify_api.verify(flags, public_inputs_path, proof_path, vk_path);
171 };
172
173 // Execute test steps
174 create_proof_and_vk();
175 verify_vk_equivalence(output_dir / "vk", ivc_vk);
176 // Test verify command
177 EXPECT_TRUE(verify_proof());
178}
179
180// WORKTODO(bbapi): Expand on this.
181TEST_F(ClientIVCAPITests, WriteVkFieldsSmokeTest)
182{
183 // Create a simple circuit bytecode
184 auto [bytecode, witness_data] = acir_bincode_mocks::create_simple_circuit_bytecode();
185
186 // Compress and write bytecode to file
187 std::filesystem::path bytecode_path = test_dir / "circuit.acir";
188 write_file(bytecode_path, bb::compress(bytecode));
189
190 // Test write_vk
192 flags.verifier_type = "standalone";
193
194 ClientIVCAPI api;
195 api.write_vk(flags, bytecode_path, test_dir);
196
197 // Verify the binary VK file was created
198 EXPECT_TRUE(std::filesystem::exists(test_dir / "vk"));
199}
200
201TEST_F(ClientIVCAPITests, WriteIVCVkSmokeTest)
202{
203 // Create a simple circuit bytecode
204 auto [bytecode, witness_data] = acir_bincode_mocks::create_simple_circuit_bytecode();
205
206 // Compress and write bytecode to file
207 std::filesystem::path bytecode_path = test_dir / "circuit.acir";
208 write_file(bytecode_path, bb::compress(bytecode));
209
210 // Set flags for VK generation
212 flags.verifier_type = "ivc";
213
214 // Call write_vk
215 ClientIVCAPI api;
216 api.write_vk(flags, bytecode_path, test_dir);
217
218 // Check that VK file exists and is non-empty
219 std::filesystem::path vk_path = test_dir / "vk";
220 ASSERT_TRUE(std::filesystem::exists(vk_path));
221 auto vk_data = read_file(vk_path);
222 ASSERT_FALSE(vk_data.empty());
223}
224
225// TODO(https://github.com/AztecProtocol/barretenberg/issues/1461): Make this test actually test # gates
226TEST_F(ClientIVCAPITests, GatesCommandSmokeTest)
227{
228 // Create a simple circuit bytecode
229 auto [bytecode, witness_data] = acir_bincode_mocks::create_simple_circuit_bytecode();
230
231 // Write compressed bytecode to file
232 std::filesystem::path bytecode_path = test_dir / "circuit.acir";
233 write_file(bytecode_path, bb::compress(bytecode));
234
236 flags.include_gates_per_opcode = true;
237
238 // Redirect stdout to a stringstream
239 std::ostringstream captured_output;
240 std::streambuf* old_cout = std::cout.rdbuf(captured_output.rdbuf());
241
242 ClientIVCAPI api;
243 api.gates(flags, bytecode_path);
244
245 // Restore stdout
246 std::cout.rdbuf(old_cout);
247 std::string output = captured_output.str();
248
249 // We rudimentarily output to this pattern:
250 // {"functions": [
251 // {
252 // "acir_opcodes": 1,
253 // "circuit_size": *,
254 // "gates_per_opcode": [*]
255 // }
256 // ]}
257 EXPECT_NE(output.find("\"functions\": ["), std::string::npos);
258 EXPECT_NE(output.find("\"acir_opcodes\": 1"), std::string::npos);
259 EXPECT_NE(output.find("\"circuit_size\": "), std::string::npos);
260 EXPECT_NE(output.find("\"gates_per_opcode\": ["), std::string::npos);
261}
262
263// Test prove_and_verify for our example IVC flow.
264TEST_F(ClientIVCAPITests, DISABLED_ProveAndVerifyCommand)
265{
266 // Create test input file
267 std::filesystem::path input_path = test_dir / "input.msgpack";
268 create_test_private_execution_steps(input_path);
269
270 ClientIVCAPI api;
271 EXPECT_TRUE(api.prove_and_verify(input_path));
272}
273
274// Check a case where precomputed VKs match
275TEST_F(ClientIVCAPITests, CheckPrecomputedVks)
276{
277 // Create test input file with precomputed VKs
278 std::filesystem::path input_path = test_dir / "input_with_vks.msgpack";
279 create_test_private_execution_steps(input_path);
280
281 ClientIVCAPI api;
282 EXPECT_TRUE(api.check_precomputed_vks(ClientIVCAPI::Flags{}, input_path));
283}
284
285// Check a case where precomputed VKs don't match
286TEST_F(ClientIVCAPITests, CheckPrecomputedVksMismatch)
287{
288 using namespace acir_format;
289
290 // Create a simple circuit
291 auto [bytecode, witness_data] = acir_bincode_mocks::create_simple_circuit_bytecode();
292
293 bbapi::BBApiRequest request;
294 auto vk_response =
295 bbapi::ClientIvcComputeStandaloneVk{ .circuit = { .name = "simple_circuit", .bytecode = bytecode } }.execute();
296 size_t vk_size = from_buffer<MegaFlavor::VerificationKey>(vk_response.bytes).to_field_elements().size();
297
298 // Create a WRONG verification key (use a different circuit)
299 auto different_bytecode = acir_bincode_mocks::create_simple_kernel(vk_size, /*is_init_kernel=*/true);
300 auto vk_response2 = bbapi::ClientIvcComputeStandaloneVk{
301 .circuit = { .name = "different_circuit", .bytecode = different_bytecode }
302 }.execute();
303 auto vk = vk_response2.bytes;
304
305 // Create PrivateExecutionStepRaw with wrong VK
308 step.bytecode = bytecode;
309 step.witness = witness_data;
310 step.vk = std::move(vk); // Wrong VK
311 step.function_name = "test_function";
312 raw_steps.push_back(std::move(step));
313
314 // Write to file using compress_and_save
315 std::filesystem::path input_path = test_dir / "input_wrong_vks.msgpack";
317
318 // Should fail because VK doesn't match
319 ClientIVCAPI api;
320 bool result = api.check_precomputed_vks(ClientIVCAPI::Flags{}, input_path);
321 EXPECT_FALSE(result);
322
323 // Check with --update_input should still fail but update the VK in the input.
324 result = api.check_precomputed_vks(ClientIVCAPI::Flags{ .update_inputs = true }, input_path);
325 EXPECT_FALSE(result);
326
327 // Check again and it should succeed with the updated VK.
328 result = api.check_precomputed_vks(ClientIVCAPI::Flags{}, input_path);
329 EXPECT_TRUE(result);
330}
ClientIVC::MegaVerificationKey get_ivc_vk(const std::filesystem::path &test_dir)
TEST_F(ClientIVCAPITests, DISABLED_ProveAndVerifyFileBasedFlow)
std::filesystem::path test_dir
void gates(const Flags &flags, const std::filesystem::path &bytecode_path) override
bool verify(const Flags &flags, const std::filesystem::path &public_inputs_path, const std::filesystem::path &proof_path, const std::filesystem::path &vk_path) override
bool check_precomputed_vks(const Flags &flags, const std::filesystem::path &input_path)
bool prove_and_verify(const std::filesystem::path &input_path)
void prove(const Flags &flags, const std::filesystem::path &input_path, const std::filesystem::path &output_dir)
void write_vk(const Flags &flags, const std::filesystem::path &bytecode_path, const std::filesystem::path &output_path) override
The verification key is responsible for storing the commitments to the precomputed (non-witness) poly...
void info(Args... args)
Definition log.hpp:74
uint8_t buffer[RANDOM_BUFFER_SIZE]
Definition engine.cpp:34
std::pair< std::vector< uint8_t >, std::vector< uint8_t > > create_simple_circuit_bytecode(size_t num_constraints=1)
Helper function to create a minimal circuit bytecode and witness for testing.
std::vector< uint8_t > create_simple_kernel(size_t vk_size, bool is_init_kernel)
Create a simple kernel circuit for IVC testing.
std::vector< uint8_t > create_kernel_witness(const std::vector< bb::fr > &app_vk_fields)
Create a kernel witness for IVC testing.
std::filesystem::path bb_crs_path()
void init_file_crs_factory(const std::filesystem::path &path)
Entry point for Barretenberg command-line interface.
std::vector< uint8_t > compress(const std::vector< uint8_t > &input)
std::vector< uint8_t > read_file(const std::string &filename, size_t bytes=0)
Definition file_io.hpp:29
void write_file(const std::string &filename, std::vector< uint8_t > const &data)
Definition file_io.hpp:58
VerifierCommitmentKey< Curve > vk
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
bool include_gates_per_opcode
Definition api.hpp:23
bool write_vk
Definition api.hpp:22
bool update_inputs
Definition api.hpp:26
std::string verifier_type
Definition api.hpp:20
This is the msgpack encoding of the objects returned by the following typescript: const stepToStruct ...
static void compress_and_save(std::vector< PrivateExecutionStepRaw > &&steps, const std::filesystem::path &output_path)
std::string name
Human-readable name for the circuit.
Compute standalone verification key for a circuit.