Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
aztec_process.cpp
Go to the documentation of this file.
1#ifndef __wasm__
2#include "aztec_process.hpp"
11#include <filesystem>
12#include <fstream>
13#include <iomanip>
14#include <nlohmann/json.hpp>
15#include <sstream>
16#include <thread>
17
18#ifdef ENABLE_AVM_TRANSPILER
19// Include avm_transpiler header
20#include <avm_transpiler.h>
21#endif
22
23namespace bb {
24
25namespace {
26
30std::vector<uint8_t> extract_bytecode(const nlohmann::json& function)
31{
32 if (!function.contains("bytecode")) {
33 throw_or_abort("Function missing bytecode field");
34 }
35
36 const auto& base64_bytecode = function["bytecode"].get<std::string>();
37 return decode_bytecode(base64_bytecode);
38}
39
43std::string compute_bytecode_hash(const std::vector<uint8_t>& bytecode)
44{
45 auto hash = crypto::sha256(bytecode);
46 std::ostringstream oss;
47 for (auto byte : hash) {
48 oss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(byte);
49 }
50 return oss.str();
51}
52
56std::filesystem::path get_cache_dir()
57{
58 const char* home = std::getenv("HOME");
59 if (!home) {
60 home = ".";
61 }
62 std::filesystem::path cache_dir = std::filesystem::path(home) / ".bb" / BB_VERSION / "vk_cache";
63 std::filesystem::create_directories(cache_dir);
64 return cache_dir;
65}
66
70bool is_private_constrained_function(const nlohmann::json& function)
71{
72 bool is_public = false;
73 bool is_unconstrained = false;
74
75 // Check custom_attributes for "public"
76 if (function.contains("custom_attributes") && function["custom_attributes"].is_array()) {
77 for (const auto& attr : function["custom_attributes"]) {
78 if (attr.is_string() && attr.get<std::string>() == "public") {
79 is_public = true;
80 break;
81 }
82 }
83 }
84
85 // Check is_unconstrained
86 if (function.contains("is_unconstrained") && function["is_unconstrained"].is_boolean()) {
87 is_unconstrained = function["is_unconstrained"].get<bool>();
88 }
89
90 return !is_public && !is_unconstrained;
91}
92
96std::vector<uint8_t> get_or_generate_cached_app_vk(const std::filesystem::path& cache_dir,
97 const std::string& circuit_name,
98 const std::vector<uint8_t>& bytecode,
99 bool force)
100{
101 std::string hash_str = compute_bytecode_hash(bytecode);
102 std::filesystem::path vk_cache_path = cache_dir / (hash_str + ".vk");
103
104 // Check cache unless force is true
105 if (!force && std::filesystem::exists(vk_cache_path)) {
106 info("Verification key already in cache: ", hash_str);
107 return read_file(vk_cache_path);
108 }
109
110 // Generate new VK (offline / build-time helper, filesystem-cached by bytecode hash).
111 info("Generating verification key: ", hash_str);
112 auto response =
113 bbapi::ChonkComputeVk{ .circuit = { .name = circuit_name, .bytecode = bytecode }, .kind = CircuitKind::App }
114 .execute();
115
116 // Cache the VK
117 write_file(vk_cache_path, response.bytes);
118
119 return response.bytes;
120}
121
125void generate_vks_for_functions(const std::filesystem::path& cache_dir,
127 bool force)
128{
129#ifdef __wasm__
130 throw_or_abort("VK generation not supported in WASM");
131#endif
132
133 const size_t total_cpus = get_num_cpus();
134 const size_t num_functions = functions.size();
135
136 // Heuristic for nested parallelism:
137 // - actual_tasks = min(num_functions, total_cpus)
138 // - threads_per_task = min(total_cpus, max(2, total_cpus / actual_tasks * 2))
139 size_t actual_tasks = std::min(num_functions, total_cpus);
140 size_t threads_per_task = std::min(total_cpus, std::max(size_t{ 2 }, total_cpus / actual_tasks * 2));
141
142 // Track work distribution
143 std::atomic<size_t> current_function{ 0 };
144
145 // Worker function
146 auto worker = [&]() {
147 // Set thread-local concurrency for this worker
148 set_parallel_for_concurrency(threads_per_task);
149
150 // Process functions
151 size_t func_idx;
152 while ((func_idx = current_function.fetch_add(1)) < num_functions) {
153 auto* function = functions[func_idx];
154 std::string fn_name = (*function)["name"].get<std::string>();
155
156 // Get bytecode from function
157 auto bytecode = extract_bytecode(*function);
158
159 // Generate and cache VK (can use parallel_for internally)
160 get_or_generate_cached_app_vk(cache_dir, fn_name, bytecode, force);
161 }
162 };
163
164 // Spawn threads
165 std::vector<std::thread> threads;
166 threads.reserve(actual_tasks);
167
168 for (size_t i = 0; i < actual_tasks; ++i) {
169 threads.emplace_back(worker);
170 }
171
172 // Wait for completion
173 for (auto& t : threads) {
174 t.join();
175 }
176
177 // Update JSON with VKs from cache (sequential is fine here, it's fast)
178 for (auto* function : functions) {
179 std::string fn_name = (*function)["name"].get<std::string>();
180
181 // Get bytecode to compute hash
182 auto bytecode = extract_bytecode(*function);
183
184 // Read VK from cache
185 std::string hash_str = compute_bytecode_hash(bytecode);
186 std::filesystem::path vk_cache_path = cache_dir / (hash_str + ".vk");
187 auto vk_data = read_file(vk_cache_path);
188
189 // Encode to base64 and store in JSON
190 std::string encoded_vk = base64_encode(vk_data.data(), vk_data.size(), false);
191 (*function)["verification_key"] = encoded_vk;
192 }
193}
194
195} // anonymous namespace
196
200bool transpile_artifact([[maybe_unused]] const std::string& input_path, [[maybe_unused]] const std::string& output_path)
201{
202#ifdef ENABLE_AVM_TRANSPILER
203 info("Transpiling: ", input_path, " -> ", output_path);
204
205 auto result = avm_transpile_file(input_path.c_str(), output_path.c_str());
206
207 if (result.success == 0) {
208 if (result.error_message) {
209 std::string error_msg(result.error_message);
210 if (error_msg == "Contract already transpiled") {
211 // Already transpiled, copy if different paths
212 if (input_path != output_path) {
213 std::filesystem::copy_file(
214 input_path, output_path, std::filesystem::copy_options::overwrite_existing);
215 }
216 } else {
217 info("Transpilation failed: ", error_msg);
218 avm_free_result(&result);
219 return false;
220 }
221 } else {
222 info("Transpilation failed");
223 avm_free_result(&result);
224 return false;
225 }
226 }
227
228 avm_free_result(&result);
229
230 info("Transpiled: ", input_path, " -> ", output_path);
231#else
232 throw_or_abort("AVM Transpiler is not enabled. Please enable it to use bb aztec_process.");
233#endif
234 return true;
235}
236
237bool process_aztec_artifact(const std::string& input_path, const std::string& output_path, bool force)
238{
239 if (!transpile_artifact(input_path, output_path)) {
240 return false;
241 }
242
243 // Verify output exists
244 if (!std::filesystem::exists(output_path)) {
245 throw_or_abort("Output file does not exist after transpilation");
246 }
247
248 // Step 2: Generate verification keys
249 auto cache_dir = get_cache_dir();
250 info("Generating verification keys for functions in ", std::filesystem::path(output_path).filename().string());
251 info("Cache directory: ", cache_dir.string());
252
253 // Read and parse artifact JSON
254 auto artifact_content = read_file(output_path);
255 std::string artifact_str(artifact_content.begin(), artifact_content.end());
256 auto artifact_json = nlohmann::json::parse(artifact_str);
257
258 if (!artifact_json.contains("functions")) {
259 info("Warning: No functions found in artifact");
260 return true;
261 }
262
263 // Strip __aztec_nr_internals__ prefix from function names.
264 // The #[aztec] macro generates wrapper functions with this prefix; we strip it so
265 // the exported ABI exposes the original developer-written names.
266 const std::string internal_prefix = "__aztec_nr_internals__";
267 for (auto& function : artifact_json["functions"]) {
268 auto& name = function["name"];
269 if (name.is_string()) {
270 std::string fn_name = name.get<std::string>();
271 if (fn_name.size() >= internal_prefix.size() &&
272 fn_name.compare(0, internal_prefix.size(), internal_prefix) == 0) {
273 name = fn_name.substr(internal_prefix.size());
274 }
275 }
276 }
277
278 // Filter to private constrained functions
279 std::vector<nlohmann::json*> private_functions;
280 for (auto& function : artifact_json["functions"]) {
281 if (is_private_constrained_function(function)) {
282 private_functions.push_back(&function);
283 }
284 }
285
286 if (!private_functions.empty()) {
287 // Generate VKs
288 generate_vks_for_functions(cache_dir, private_functions, force);
289 } else {
290 info("No private constrained functions found");
291 }
292
293 // Write updated JSON back to file
294 std::ofstream out_file(output_path);
295 out_file << artifact_json.dump(2) << std::endl;
296 out_file.close();
297
298 info("Successfully processed: ", input_path, " -> ", output_path);
299 return true;
300}
301
302std::vector<std::string> find_contract_artifacts(const std::string& search_path)
303{
304 std::vector<std::string> artifacts;
305
306 // Recursively search for .json files in target/ directories, excluding cache/
307 for (const auto& entry : std::filesystem::recursive_directory_iterator(search_path)) {
308 if (!entry.is_regular_file()) {
309 continue;
310 }
311
312 const auto& path = entry.path();
313
314 // Must be a .json file
315 if (path.extension() != ".json") {
316 continue;
317 }
318
319 // Must be in a target/ directory
320 std::string path_str = path.string();
321 if (path_str.find("/target/") == std::string::npos && path_str.find("\\target\\") == std::string::npos) {
322 continue;
323 }
324
325 // Exclude cache directories and function artifact temporaries
326 if (path_str.find("/cache/") != std::string::npos || path_str.find("\\cache\\") != std::string::npos ||
327 path_str.find(".function_artifact_") != std::string::npos) {
328 continue;
329 }
330
331 artifacts.push_back(path.string());
332 }
333
334 return artifacts;
335}
336
337bool process_all_artifacts(const std::string& search_path, bool force)
338{
339 auto artifacts = find_contract_artifacts(search_path);
340
341 if (artifacts.empty()) {
342 info("No contract artifacts found in '", search_path, "'.");
343 return false;
344 }
345
346 info("Found ", artifacts.size(), " contract artifact(s) to process");
347
348 bool all_success = true;
349 for (const auto& artifact : artifacts) {
350 // Process in-place (input == output)
351 if (!process_aztec_artifact(artifact, artifact, force)) {
352 all_success = false;
353 }
354 }
355
356 if (all_success) {
357 info("Contract postprocessing complete!");
358 }
359
360 return all_success;
361}
362
363bool get_cache_paths(const std::string& input_path)
364{
365 try {
366 // Verify input exists
367 if (!std::filesystem::exists(input_path)) {
368 throw_or_abort("Input file does not exist: " + input_path);
369 }
370
371 // Read and parse artifact JSON
372 auto artifact_content = read_file(input_path);
373 std::string artifact_str(artifact_content.begin(), artifact_content.end());
374 auto artifact_json = nlohmann::json::parse(artifact_str);
375
376 if (!artifact_json.contains("functions")) {
377 // No functions, but not an error
378 return true;
379 }
380
381 // Get cache directory
382 auto cache_dir = get_cache_dir();
383
384 // Find all private constrained functions and output their cache paths
385 for (const auto& function : artifact_json["functions"]) {
386 if (!is_private_constrained_function(function)) {
387 continue;
388 }
389
390 std::string fn_name = function["name"].get<std::string>();
391 auto bytecode = extract_bytecode(function);
392 std::string hash_str = compute_bytecode_hash(bytecode);
393 std::filesystem::path vk_cache_path = cache_dir / (hash_str + ".vk");
394
395 // Output format: hash:cache_path:function_name
396 std::cout << hash_str << ":" << vk_cache_path.string() << ":" << fn_name << std::endl;
397 }
398
399 return true;
400 } catch (const std::exception& e) {
401 info("Error getting cache paths: ", e.what());
402 return false;
403 }
404}
405
406} // namespace bb
407#endif
std::string base64_encode(unsigned char const *bytes_to_encode, size_t in_len, bool url)
Definition base64.cpp:117
Chonk-specific command definitions for the Barretenberg RPC API.
#define info(...)
Definition log.hpp:93
std::vector< uint8_t > bytecode
std::vector< uint8_t > decode_bytecode(const std::string &base64_bytecode)
Sha256Hash sha256(const ByteContainer &input)
SHA-256 hash function (FIPS 180-4)
Definition sha256.cpp:150
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
bool transpile_artifact(const std::string &input_path, const std::string &output_path)
Transpile the artifact file (or copy if transpiler not enabled)
bool process_all_artifacts(const std::string &search_path, bool force)
Process all discovered contract artifacts in a directory tree.
bool get_cache_paths(const std::string &input_path)
Get cache paths for all verification keys in an artifact.
bool process_aztec_artifact(const std::string &input_path, const std::string &output_path, bool force)
Process Aztec contract artifacts: transpile and generate verification keys.
size_t get_num_cpus()
Definition thread.cpp:34
std::vector< std::string > find_contract_artifacts(const std::string &search_path)
Find all contract artifacts in target/ directories.
const char * BB_VERSION
Definition version.hpp:14
std::vector< uint8_t > read_file(const std::string &filename, size_t bytes=0)
Definition file_io.hpp:31
void set_parallel_for_concurrency(size_t num_cores)
Definition thread.cpp:24
void write_file(const std::string &filename, std::span< const uint8_t > data)
Definition file_io.hpp:101
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::string name
void throw_or_abort(std::string const &err)
VectorField result