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