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 auto app_vk = app_vk_response.bytes;
55 auto app_vk_fields = from_buffer<MegaFlavor::VerificationKey>(app_vk).to_field_elements();
56
57 // Now create a kernel circuit that verifies the app circuit
58 auto kernel_bytecode = acir_bincode_mocks::create_simple_kernel(app_vk_fields.size(), /*is_init_kernel=*/true);
59 auto kernel_witness_data = acir_bincode_mocks::create_kernel_witness(app_vk_fields);
60
61 auto kernel_vk_response = bbapi::ClientIvcComputeStandaloneVk{
62 .circuit = { .name = "kernel_circuit", .bytecode = kernel_bytecode }
63 }.execute();
64 auto kernel_vk = kernel_vk_response.bytes;
65
66 // Create PrivateExecutionStepRaw for the kernel
68 raw_steps.push_back(
69 { .bytecode = app_bytecode, .witness = app_witness_data, .vk = app_vk, .function_name = "app_function" });
70 raw_steps.push_back({ .bytecode = kernel_bytecode,
71 .witness = kernel_witness_data,
72 .vk = kernel_vk,
73 .function_name = "kernel_function" });
74
76}
77} // namespace
78
79class ClientIVCAPITests : public ::testing::Test {
80 protected:
82
83 void SetUp() override
84 {
85 const auto* info = ::testing::UnitTest::GetInstance()->current_test_info();
86 test_dir = get_test_dir(info->name());
87 }
88
89 void TearDown() override
90 {
91 if (std::filesystem::exists(test_dir)) {
92 std::filesystem::remove_all(test_dir);
93 }
94 }
95
96 std::filesystem::path test_dir;
97};
98
99namespace bb {
100std::vector<uint8_t> compress(const std::vector<uint8_t>& input);
101}
102
103// Used to get a mock IVC vk.
104ClientIVC::MegaVerificationKey get_ivc_vk(const std::filesystem::path& test_dir)
105{
106 auto [app_bytecode, app_witness_data] = acir_bincode_mocks::create_simple_circuit_bytecode();
107 bbapi::BBApiRequest request;
108 auto app_vk_response =
109 bbapi::ClientIvcComputeStandaloneVk{ .circuit = { .name = "app_circuit", .bytecode = app_bytecode } }.execute();
110 auto app_vk = app_vk_response.bytes;
111 auto app_vk_fields = from_buffer<MegaFlavor::VerificationKey>(app_vk).to_field_elements();
112 // Use this to get the size of the vk.
113 auto bytecode = acir_bincode_mocks::create_simple_kernel(app_vk_fields.size(), /*is_init_kernel=*/false);
114 std::filesystem::path bytecode_path = test_dir / "circuit.acir";
115 write_file(bytecode_path, bb::compress(bytecode));
116
117 ClientIVCAPI::Flags write_vk_flags;
118 write_vk_flags.verifier_type = "ivc";
119 write_vk_flags.output_format = "bytes";
120
121 ClientIVCAPI api;
122 api.write_vk(write_vk_flags, bytecode_path, test_dir);
123
124 return from_buffer<ClientIVC::MegaVerificationKey>(read_file(test_dir / "vk"));
125};
126
127// Test the ClientIVCAPI::prove flow, making sure --write_vk
128// returns the same output as our ivc VK generation.
129TEST_F(ClientIVCAPITests, DISABLED_ProveAndVerifyFileBasedFlow)
130{
131 auto ivc_vk = get_ivc_vk(test_dir);
132
133 // Create test input file
134 std::filesystem::path input_path = test_dir / "input.msgpack";
135 create_test_private_execution_steps(input_path);
136
137 std::filesystem::path output_dir = test_dir / "output";
138 std::filesystem::create_directories(output_dir);
139
140 // Helper lambda to create proof and VK files
141 auto create_proof_and_vk = [&]() {
143 flags.write_vk = true;
144 ClientIVCAPI api;
145 api.prove(flags, input_path, output_dir);
146 };
147
148 // Helper lambda to verify VK equivalence
149 auto verify_vk_equivalence = [&](const std::filesystem::path& vk1_path, const ClientIVC::MegaVerificationKey& vk2) {
150 auto vk1_data = read_file(vk1_path);
151 auto vk1 = from_buffer<ClientIVC::MegaVerificationKey>(vk1_data);
152 ASSERT_TRUE(msgpack::msgpack_check_eq(vk1, vk2, "VK from prove should match VK from write_vk"));
153 };
154
155 // Helper lambda to verify proof
156 auto verify_proof = [&]() {
157 std::filesystem::path proof_path = output_dir / "proof";
158 std::filesystem::path vk_path = output_dir / "vk";
159 std::filesystem::path public_inputs_path; // Not used for ClientIVC
160
162 ClientIVCAPI verify_api;
163 return verify_api.verify(flags, public_inputs_path, proof_path, vk_path);
164 };
165
166 // Execute test steps
167 create_proof_and_vk();
168 verify_vk_equivalence(output_dir / "vk", ivc_vk);
169 // Test verify command
170 EXPECT_TRUE(verify_proof());
171}
172
173// WORKTODO(bbapi): Expand on this.
174TEST_F(ClientIVCAPITests, WriteVkFieldsSmokeTest)
175{
176 // Create a simple circuit bytecode
177 auto [bytecode, witness_data] = acir_bincode_mocks::create_simple_circuit_bytecode();
178
179 // Compress and write bytecode to file
180 std::filesystem::path bytecode_path = test_dir / "circuit.acir";
181 write_file(bytecode_path, bb::compress(bytecode));
182
183 // Test write_vk with fields output format
185 flags.verifier_type = "standalone";
186 flags.output_format = "fields";
187
188 ClientIVCAPI api;
189 api.write_vk(flags, bytecode_path, test_dir);
190
191 // Read and verify the fields format
192 auto vk_data = read_file(test_dir / "vk_fields.json");
193 std::string vk_str(vk_data.begin(), vk_data.end());
194 // Just check that this looks a bit like JSON.
195 EXPECT_NE(vk_str.find('['), std::string::npos);
196 EXPECT_NE(vk_str.find(']'), std::string::npos);
197}
198
199TEST_F(ClientIVCAPITests, WriteIVCVkSmokeTest)
200{
201 // Create a simple circuit bytecode
202 auto [bytecode, witness_data] = acir_bincode_mocks::create_simple_circuit_bytecode();
203
204 // Compress and write bytecode to file
205 std::filesystem::path bytecode_path = test_dir / "circuit.acir";
206 write_file(bytecode_path, bb::compress(bytecode));
207
208 // Set flags for VK generation
210 flags.verifier_type = "ivc";
211 flags.output_format = "bytes";
212
213 // Call write_vk
214 ClientIVCAPI api;
215 api.write_vk(flags, bytecode_path, test_dir);
216
217 // Check that VK file exists and is non-empty
218 std::filesystem::path vk_path = test_dir / "vk";
219 ASSERT_TRUE(std::filesystem::exists(vk_path));
220 auto vk_data = read_file(vk_path);
221 ASSERT_FALSE(vk_data.empty());
222}
223
224// TODO(https://github.com/AztecProtocol/barretenberg/issues/1461): Make this test actually test # gates
225TEST_F(ClientIVCAPITests, GatesCommandSmokeTest)
226{
227 // Create a simple circuit bytecode
228 auto [bytecode, witness_data] = acir_bincode_mocks::create_simple_circuit_bytecode();
229
230 // Write compressed bytecode to file
231 std::filesystem::path bytecode_path = test_dir / "circuit.acir";
232 write_file(bytecode_path, bb::compress(bytecode));
233
235 flags.include_gates_per_opcode = true;
236
237 // Redirect stdout to a stringstream
238 std::ostringstream captured_output;
239 std::streambuf* old_cout = std::cout.rdbuf(captured_output.rdbuf());
240
241 ClientIVCAPI api;
242 api.gates(flags, bytecode_path);
243
244 // Restore stdout
245 std::cout.rdbuf(old_cout);
246 std::string output = captured_output.str();
247
248 // We rudimentarily output to this pattern:
249 // {"functions": [
250 // {
251 // "acir_opcodes": 1,
252 // "circuit_size": *,
253 // "gates_per_opcode": [*]
254 // }
255 // ]}
256 EXPECT_NE(output.find("\"functions\": ["), std::string::npos);
257 EXPECT_NE(output.find("\"acir_opcodes\": 1"), std::string::npos);
258 EXPECT_NE(output.find("\"circuit_size\": "), std::string::npos);
259 EXPECT_NE(output.find("\"gates_per_opcode\": ["), std::string::npos);
260}
261
262// Test prove_and_verify for our example IVC flow.
263TEST_F(ClientIVCAPITests, DISABLED_ProveAndVerifyCommand)
264{
265 // Create test input file
266 std::filesystem::path input_path = test_dir / "input.msgpack";
267 create_test_private_execution_steps(input_path);
268
269 ClientIVCAPI api;
270 EXPECT_TRUE(api.prove_and_verify(input_path));
271}
272
273// Check a case where precomputed VKs match
274TEST_F(ClientIVCAPITests, CheckPrecomputedVks)
275{
276 // Create test input file with precomputed VKs
277 std::filesystem::path input_path = test_dir / "input_with_vks.msgpack";
278 create_test_private_execution_steps(input_path);
279
280 ClientIVCAPI api;
281 EXPECT_TRUE(api.check_precomputed_vks(ClientIVCAPI::Flags{}, input_path));
282}
283
284// Check a case where precomputed VKs don't match
285TEST_F(ClientIVCAPITests, CheckPrecomputedVksMismatch)
286{
287 using namespace acir_format;
288
289 // Create a simple circuit
290 auto [bytecode, witness_data] = acir_bincode_mocks::create_simple_circuit_bytecode();
291
292 bbapi::BBApiRequest request;
293 auto vk_response =
294 bbapi::ClientIvcComputeStandaloneVk{ .circuit = { .name = "simple_circuit", .bytecode = bytecode } }.execute();
295 size_t vk_size = from_buffer<MegaFlavor::VerificationKey>(vk_response.bytes).to_field_elements().size();
296
297 // Create a WRONG verification key (use a different circuit)
298 auto different_bytecode = acir_bincode_mocks::create_simple_kernel(vk_size, /*is_init_kernel=*/true);
299 auto vk_response2 = bbapi::ClientIvcComputeStandaloneVk{
300 .circuit = { .name = "different_circuit", .bytecode = different_bytecode }
301 }.execute();
302 auto vk = vk_response2.bytes;
303
304 // Create PrivateExecutionStepRaw with wrong VK
307 step.bytecode = bytecode;
308 step.witness = witness_data;
309 step.vk = std::move(vk); // Wrong VK
310 step.function_name = "test_function";
311 raw_steps.push_back(std::move(step));
312
313 // Write to file using compress_and_save
314 std::filesystem::path input_path = test_dir / "input_wrong_vks.msgpack";
316
317 // Should fail because VK doesn't match
318 ClientIVCAPI api;
319 bool result = api.check_precomputed_vks(ClientIVCAPI::Flags{}, input_path);
320 EXPECT_FALSE(result);
321
322 // Check with --update_input should still fail but update the VK in the input.
323 result = api.check_precomputed_vks(ClientIVCAPI::Flags{ .update_inputs = true }, input_path);
324 EXPECT_FALSE(result);
325
326 // Check again and it should succeed with the updated VK.
327 result = api.check_precomputed_vks(ClientIVCAPI::Flags{}, input_path);
328 EXPECT_TRUE(result);
329}
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:70
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::pair< std::vector< uint8_t >, std::vector< uint8_t > > create_simple_circuit_bytecode()
Helper function to create a minimal circuit bytecode and witness for 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
bool msgpack_check_eq(const T &v1, const T &v2, const std::string_view &error_message)
Ensures that two msgpack objects are equal by applying the msgpack method to both and comparing the r...
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
bool include_gates_per_opcode
Definition api.hpp:24
bool write_vk
Definition api.hpp:23
bool update_inputs
Definition api.hpp:26
std::string verifier_type
Definition api.hpp:21
std::string output_format
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.