1pub use paste;
2pub use serde_json;
3use std::{
4 env, fs,
5 path::{Path, PathBuf},
6 process::Command,
7};
8
9pub mod convert_type;
10pub use convert_type::*;
11
12#[doc(hidden)]
13pub mod __macro_deps {
14 pub use anyhow;
15}
16
17#[macro_export]
19macro_rules! witness {
20 ($x: ident) => {
21 $crate::paste::paste! {
22 #[allow(non_upper_case_globals)]
23 const [<$x _CIRCUIT_DATA>]: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/witnesscalc/src/", stringify!($x), ".dat"));
24 #[link(name = "witnesscalc_" [<$x>], kind = "static")]
25 extern "C" {
26 fn [<witnesscalc_ $x>](
27 circuit_buffer: *const std::os::raw::c_char,
28 circuit_size: std::ffi::c_ulong,
29 json_buffer: *const std::os::raw::c_char,
30 json_size: std::ffi::c_ulong,
31 wtns_buffer: *mut std::os::raw::c_char,
32 wtns_size: *mut std::ffi::c_ulong,
33 error_msg: *mut std::os::raw::c_char,
34 error_msg_maxsize: std::ffi::c_ulong,
35 ) -> std::ffi::c_int;
36 }
37 }
38 $crate::paste::item! {
39 pub fn [<$x _witness>](json_input: &str) -> $crate::__macro_deps::anyhow::Result<Vec<u8>> {
40 const WITNESSCALC_OK: std::ffi::c_int = 0x0;
42 const WITNESSCALC_ERROR_SHORT_BUFFER: std::ffi::c_int = 0x2;
43
44 println!("Generating witness for circuit {}", stringify!($x));
45 unsafe {
46 let json_input = std::ffi::CString::new(json_input).map_err(|e| $crate::__macro_deps::anyhow::anyhow!("Failed to convert JSON input to CString: {}", e))?;
47 let json_size = json_input.as_bytes().len() as std::ffi::c_ulong;
48
49 let circuit_buffer = [<$x _CIRCUIT_DATA>].as_ptr() as *const std::ffi::c_char;
50 let circuit_size = [<$x _CIRCUIT_DATA>].len() as std::ffi::c_ulong;
51
52 let mut error_msg = vec![0u8; 256]; let error_msg_ptr = error_msg.as_mut_ptr() as *mut std::ffi::c_char;
54
55 let mut probe_buffer = vec![0u8; 1024]; let mut wtns_size: std::ffi::c_ulong = probe_buffer.len() as std::ffi::c_ulong;
59
60 let result = [<witnesscalc_ $x>](
61 circuit_buffer,
62 circuit_size,
63 json_input.as_ptr(),
64 json_size,
65 probe_buffer.as_mut_ptr() as *mut _,
66 &mut wtns_size as *mut _,
67 error_msg_ptr,
68 error_msg.len() as u64,
69 );
70
71 let final_buffer = if result == WITNESSCALC_ERROR_SHORT_BUFFER {
73 let required_size = wtns_size as usize;
75 println!("Witness requires {} bytes, allocating and retrying...", required_size);
76
77 let mut wtns_buffer = vec![0u8; required_size];
78 let mut wtns_size: std::ffi::c_ulong = required_size as std::ffi::c_ulong;
79
80 let result = [<witnesscalc_ $x>](
81 circuit_buffer,
82 circuit_size,
83 json_input.as_ptr(),
84 json_size,
85 wtns_buffer.as_mut_ptr() as *mut _,
86 &mut wtns_size as *mut _,
87 error_msg_ptr,
88 error_msg.len() as u64,
89 );
90
91 if result != WITNESSCALC_OK {
92 let error_string = std::ffi::CStr::from_ptr(error_msg_ptr)
93 .to_string_lossy()
94 .into_owned();
95 return Err($crate::__macro_deps::anyhow::anyhow!("Witness generation failed: {}", error_string));
96 }
97
98 wtns_buffer[..wtns_size as usize].to_vec()
99 } else if result == WITNESSCALC_OK {
100 probe_buffer[..wtns_size as usize].to_vec()
102 } else {
103 let error_string = std::ffi::CStr::from_ptr(error_msg_ptr)
105 .to_string_lossy()
106 .into_owned();
107 return Err($crate::__macro_deps::anyhow::anyhow!("Witness generation failed: {}", error_string));
108 };
109
110 Ok(final_buffer)
111 }
112 }
113 }
114 };
115}
116
117const WITNESSCALC_BUILD_SCRIPT: &str = include_str!("../clone_witnesscalc.sh");
118
119pub fn build_and_link(circuits_dir: &str) {
120 let target = env::var("TARGET").expect("Cargo did not provide the TARGET environment variable");
121
122 let out_dir = env::var("OUT_DIR").expect("OUT_DIR not set");
123 let lib_dir = Path::new(&out_dir)
124 .join("witnesscalc")
125 .join("package")
126 .join("lib");
127
128 if !Path::is_dir(Path::new(circuits_dir)) {
129 panic!("circuits_dir must be a directory");
130 }
131 println!("cargo:rerun-if-changed={}", circuits_dir);
132
133 let witnesscalc_path = Path::new(&out_dir).join(Path::new("witnesscalc"));
134 if !witnesscalc_path.exists() {
136 let witnesscalc_script_path = Path::new(&out_dir).join(Path::new("clone_witnesscalc.sh"));
137 fs::write(&witnesscalc_script_path, WITNESSCALC_BUILD_SCRIPT)
138 .expect("Failed to write build script");
139 Command::new("sh")
140 .arg(witnesscalc_script_path.to_str().unwrap())
141 .spawn()
142 .expect("Failed to spawn witnesscalc build")
143 .wait()
144 .expect("witnesscalc build errored");
145 }
146
147 println!("Detected target: {}", target);
148 let gmp_build_target = match target.as_str() {
150 "aarch64-apple-ios" => "ios",
151 "aarch64-apple-ios-sim" => "ios_simulator",
152 "x86_64-apple-ios" => "ios_simulator",
153 "x86_64-linux-android" => "android_x86_64",
154 "i686-linux-android" => "android_x86_64",
155 "armv7-linux-androideabi" => "android",
156 "aarch64-linux-android" => "android",
157 "aarch64-apple-darwin" => "host", _ => "host",
159 };
160
161 let gmp_lib_folder = match target.as_str() {
162 "aarch64-apple-ios" => "package_ios_arm64",
163 "aarch64-apple-ios-sim" => "package_iphone_simulator_arm64",
164 "x86_64-apple-ios" => "package_iphone_simulator_x86_64",
165 "x86_64-linux-android" => "package_android_x86_64",
166 "i686-linux-android" => "package_android_x86_64",
167 "armv7-linux-androideabi" => "package_android_arm64",
168 "aarch64-linux-android" => "package_android_arm64",
169 _ => "package",
170 };
171 let witnesscalc_build_target = match target.as_str() {
173 "aarch64-apple-ios" => "ios",
174 "aarch64-apple-ios-sim" => "ios_simulator_arm64",
175 "x86_64-apple-ios" => "ios_simulator_x86_64",
176 "x86_64-linux-android" => "android_x86_64",
177 "i686-linux-android" => "android_x86_64",
178 "armv7-linux-androideabi" => "android",
179 "aarch64-linux-android" => "android",
180 "aarch64-apple-darwin" => "arm64_host",
181 _ => "host",
182 };
183
184 let gmp_dir = witnesscalc_path.join("depends").join("gmp");
186 let target_dir = gmp_dir.join(gmp_lib_folder);
187 if !target_dir.exists() {
188 Command::new("bash")
189 .current_dir(&witnesscalc_path)
190 .arg("./build_gmp.sh")
191 .arg(gmp_build_target)
192 .spawn()
193 .expect("Failed to spawn build_gmp.sh")
194 .wait()
195 .expect("build_gmp.sh errored");
196 }
197
198 let circuit_files = fs::read_dir(circuits_dir)
200 .expect("Failed to read circuits directory")
201 .map(|entry| entry.unwrap().path())
202 .filter(|path| path.extension().is_some() && path.extension().unwrap() == "cpp")
203 .collect::<Vec<_>>();
204
205 let mut v2_1_0_circuit_files: Vec<PathBuf> = Vec::new();
206 let mut v2_2_0_circuit_files: Vec<PathBuf> = Vec::new();
207
208 circuit_files.iter().for_each(|path| {
210 let circuit_name = path.file_stem().unwrap().to_str().unwrap();
211 let circuit_dat = path.with_extension("dat");
212 let circuit_dat_name = circuit_dat.file_name().unwrap().to_str().unwrap();
213 let circuit_dat_dest = witnesscalc_path.join("src").join(circuit_dat_name);
214 fs::copy(&circuit_dat, &circuit_dat_dest).expect("Failed to copy circuit .dat file");
215 let circuit_cpp = fs::read_to_string(path).expect("Failed to read circuit .cpp file");
217 let circuit_cpp = circuit_cpp.replace(
218 "#include \"calcwit.hpp\"",
219 "#include \"calcwit.hpp\"\nnamespace CIRCUIT_NAME {",
220 );
221 let circuit_cpp = circuit_cpp + "\n}";
222 let circuit_cpp_name = witnesscalc_path.join("src").join(circuit_name);
223 let circuit_cpp_dest = circuit_cpp_name.with_extension("cpp");
224 fs::write(&circuit_cpp_dest, &circuit_cpp).expect("Failed to write circuit .cpp file");
225
226 let circuit_cpp_str = &circuit_cpp;
227 if circuit_cpp_str.contains("uint get_size_of_bus_field_map() {return 0;}") {
228 v2_2_0_circuit_files.push(path.clone());
229 } else {
230 v2_1_0_circuit_files.push(path.clone());
231 }
232 });
233
234 build_for_circuits_with_different_versions(
235 &v2_1_0_circuit_files,
236 &witnesscalc_path,
237 &witnesscalc_build_target,
238 );
239 if v2_2_0_circuit_files.len() > 0 {
240 Command::new("git")
241 .arg("checkout")
242 .arg("v2.2.0")
243 .current_dir(&witnesscalc_path)
244 .spawn()
245 .expect("Failed to spawn git checkout v2.2.0")
246 .wait()
247 .expect("git checkout v2.2.0 errored");
248 build_for_circuits_with_different_versions(
249 &v2_2_0_circuit_files,
250 &witnesscalc_path,
251 &witnesscalc_build_target,
252 );
253 }
254
255 #[cfg(target_os = "macos")]
258 {
259 println!("cargo:rustc-link-lib=c++"); }
261 #[cfg(not(target_os = "macos"))]
262 {
263 println!("cargo:rustc-link-lib=stdc++"); }
265 println!("cargo:rustc-link-lib=static=gmp");
267 println!("cargo:rustc-link-lib=static=fr");
268 println!(
270 "cargo:rustc-link-search=native={}",
271 lib_dir.to_string_lossy()
272 );
273
274 if !(env::var("CARGO_CFG_TARGET_OS").unwrap().contains("ios")
275 || env::var("CARGO_CFG_TARGET_OS").unwrap().contains("android"))
276 {
277 println!("cargo:rustc-link-lib=dylib=fr");
278 println!("cargo:rustc-link-lib=dylib=gmp");
279 }
280}
281
282fn build_for_circuits_with_different_versions(
283 circuit_files: &Vec<PathBuf>,
284 witnesscalc_path: &Path,
285 witnesscalc_build_target: &str,
286) {
287 circuit_files.iter().for_each(|path| {
288 let circuit_name = path.file_stem().unwrap().to_str().unwrap();
289 let template_path = witnesscalc_path
291 .join("src")
292 .join("witnesscalc_template.cpp");
293 let template = fs::read_to_string(&template_path).expect("Failed to read template file");
294 let template = template.replace("@CIRCUIT_NAME@", circuit_name);
295 let template_dest = witnesscalc_path
296 .join("src")
297 .join(format!("witnesscalc_{}.cpp", circuit_name));
298 fs::write(&template_dest, template).expect("Failed to write the templated .cpp file");
299 let template_path = witnesscalc_path.join("src").join("witnesscalc_template.h");
301 let template = fs::read_to_string(&template_path).expect("Failed to read template file");
302 let template = template
303 .replace("@CIRCUIT_NAME@", circuit_name)
304 .replace("@CIRCUIT_NAME_CAPS@", &circuit_name.to_uppercase());
305 let template_dest = witnesscalc_path
306 .join("src")
307 .join(format!("witnesscalc_{}.h", circuit_name));
308 fs::write(&template_dest, template).expect("Failed to write the templated .h file");
309 });
310
311 let circuit_names = circuit_files
313 .iter()
314 .map(|path| path.file_stem().unwrap().to_str().unwrap())
315 .collect::<Vec<_>>();
316
317 let circuit_names_semicolon = circuit_names.join(";");
318
319 let make_process = Command::new("make")
320 .env("CIRCUIT_NAMES", circuit_names_semicolon)
321 .arg(witnesscalc_build_target)
322 .current_dir(&witnesscalc_path)
323 .output()
324 .expect("Failed to execute make arm64_host");
325
326 if !make_process.status.success() {
327 eprintln!(
328 "Make command failed with exit code: {}",
329 make_process.status
330 );
331 eprintln!("stdout: {}", String::from_utf8_lossy(&make_process.stdout));
332 eprintln!("stderr: {}", String::from_utf8_lossy(&make_process.stderr));
333
334 let lib_dir = witnesscalc_path.join("package").join("lib");
336 let mut all_libs_exist = true;
337
338 for circuit_name in &circuit_names {
339 let lib_path = lib_dir.join(format!("libwitnesscalc_{}.a", circuit_name));
340 if !lib_path.exists() {
341 eprintln!("Warning: Library {} was not built", lib_path.display());
342 all_libs_exist = false;
343 }
344 }
345
346 if !all_libs_exist {
347 panic!("Make command failed and required libraries are missing");
348 } else {
349 eprintln!("Warning: Make command failed but required libraries exist. Continuing...");
350 }
351 }
352
353 circuit_names.iter().for_each(|circuit_name| {
355 println!("cargo:rustc-link-lib=static=witnesscalc_{}", circuit_name);
356 });
357
358 if !(env::var("CARGO_CFG_TARGET_OS").unwrap().contains("ios")
359 || env::var("CARGO_CFG_TARGET_OS").unwrap().contains("android"))
360 {
361 circuit_names.iter().for_each(|circuit_name| {
362 println!("cargo:rustc-link-lib=dylib=witnesscalc_{}", circuit_name);
363 });
364 }
365}