use std::fs;
use std::path::{Path, PathBuf};
use anyhow::{bail, Context, Result};
#[derive(Debug)]
pub struct NeedsCompressorError {
pub n_bits: usize,
}
impl std::fmt::Display for NeedsCompressorError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Recursive1 circuit exceeds 17-bit threshold (n_bits={}); a compressor is needed", self.n_bits)
}
}
impl std::error::Error for NeedsCompressorError {}
#[derive(Debug)]
pub struct RecursiveTooSmallError {
pub n_bits: usize,
pub n_used: usize,
}
impl std::fmt::Display for RecursiveTooSmallError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Recursive1 packs to 2^{} (n_used={}) below the shared recursive domain; \
compressor nQueries must grow",
self.n_bits, self.n_used
)
}
}
impl std::error::Error for RecursiveTooSmallError {}
use serde_json::Value;
use pil2_pilout::pilout_proxy::PilOutProxy;
use pil2_stark_recurser::plonk2pil::r1cs_types::PlonkOptions;
use pil2_stark_recurser::plonk2pil::{self, PlonkResult};
use crate::proving_key::bctree;
use crate::io::fixed_cols;
use crate::output::witness_gen::WitnessTracker;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RecursiveTemplate {
Compressor,
Recursive1,
Recursive2,
}
impl RecursiveTemplate {
pub fn as_str(&self) -> &'static str {
match self {
Self::Compressor => "compressor",
Self::Recursive1 => "recursive1",
Self::Recursive2 => "recursive2",
}
}
pub fn tera_template(&self) -> &'static str {
match self {
Self::Compressor => "vadcop/compressor.circom.tera",
Self::Recursive1 => "vadcop/recursive1.circom.tera",
Self::Recursive2 => "vadcop/recursive2.circom.tera",
}
}
pub fn ejs_template(&self) -> &'static str {
match self {
Self::Compressor => "src/vadcop/templates/compressor.circom.ejs",
Self::Recursive1 => "src/vadcop/templates/recursive1.circom.ejs",
Self::Recursive2 => "src/vadcop/templates/recursive2.circom.ejs",
}
}
}
pub struct RecursiveSetupConfig<'a> {
pub build_dir: &'a str,
pub template: RecursiveTemplate,
pub airgroup_name: &'a str,
pub airgroup_id: usize,
pub air_id: usize,
pub air_name: &'a str,
pub global_info: &'a Value,
pub const_root: &'a [String; 4],
pub verification_keys: &'a [Vec<Vec<String>>],
pub stark_info: &'a Value,
pub verifier_info: &'a Value,
pub stark_struct: Option<&'a Value>,
pub has_compressor: bool,
pub hash: &'a str,
pub stark_info_path: Option<&'a std::path::Path>,
pub defer_witness_lib: bool,
pub existing_pil_info: Option<(Value, Value, Value)>,
pub circom_exec: &'a str,
pub circuits_gl_path: &'a str,
pub recurser_circuits_path: &'a str,
pub std_pil_path: &'a str,
pub recurser_pil_path: &'a str,
pub circom_helpers_dir: &'a str,
}
pub struct RecursiveSetupResult {
pub const_root: [u64; 4],
pub pil_str: String,
pub stark_info: Option<Value>,
pub verifier_info: Option<Value>,
pub expressions_info: Option<Value>,
pub n_bits: usize,
pub n_used: usize,
pub witness_lib_params: Option<(String, String)>,
}
pub fn gen_recursive_setup(
config: &RecursiveSetupConfig<'_>,
witness_tracker: &WitnessTracker,
) -> Result<RecursiveSetupResult> {
let template = config.template;
let template_str = template.as_str();
let (verifier_name, name_filename, files_dir, input_challenges, verkey_input, enable_input) =
resolve_names_and_paths(config)?;
let airgroup_pil_name = match template {
RecursiveTemplate::Compressor => {
format!("{}_{}_{}", config.airgroup_name, config.air_name, template_str)
}
RecursiveTemplate::Recursive1 => {
format!("{}_{}_{}", config.airgroup_name, config.air_name, template_str)
}
RecursiveTemplate::Recursive2 => {
format!("{}_{}_{}", config.airgroup_name, config.air_name, "recursive1")
}
};
let circom_dir = PathBuf::from(config.build_dir).join("circom");
let build_dir_path = PathBuf::from(config.build_dir).join("build");
let pil_dir = PathBuf::from(config.build_dir).join("pil");
fs::create_dir_all(&circom_dir)?;
fs::create_dir_all(&build_dir_path)?;
fs::create_dir_all(&pil_dir)?;
fs::create_dir_all(&files_dir)?;
let const_root_circuit: [String; 4] = if config.const_root.iter().all(|s| s.is_empty()) {
["0".to_string(), "0".to_string(), "0".to_string(), "0".to_string()]
} else {
config.const_root.clone()
};
let pil2circom_opts = crate::io::recurser::Pil2CircomOptions {
skip_main: true,
verkey_input,
enable_input,
input_challenges,
hash: config.hash.to_string(),
};
let verifier_path = circom_dir.join(&verifier_name);
let verifier_filenames = vec![verifier_name.clone()];
let circom_out_path = circom_dir.join(format!("{}.circom", name_filename));
let r1cs_path = build_dir_path.join(format!("{}.r1cs", name_filename));
let dat_src = build_dir_path.join(format!("{}_cpp", name_filename)).join(format!("{}.dat", name_filename));
let dat_dst = files_dir.join(format!("{}.dat", template_str));
let plonk_airgroup_name = match template {
RecursiveTemplate::Recursive2 => "Recursive2".to_string(),
_ => airgroup_pil_name.clone(),
};
let mut plonk_opts = PlonkOptions {
airgroup_name: Some(plonk_airgroup_name),
max_constraint_degree: None,
hash_id: config.hash.to_string(),
merge_copies: true,
};
if template == RecursiveTemplate::Compressor {
plonk_opts.max_constraint_degree = Some(5);
}
let type_compressor = match template {
RecursiveTemplate::Compressor => "compressor",
_ => "aggregation",
};
let run_circom_and_plonk = |effective_si: &serde_json::Value| -> Result<PlonkResult> {
let verifier_circom =
crate::io::recurser::pil2circom(&const_root_circuit, effective_si, config.verifier_info, &pil2circom_opts)
.context("pil2circom failed in recursive setup")?;
fs::write(&verifier_path, &verifier_circom)?;
let gen_opts = crate::io::recurser::GenCircomOptions {
airgroup_id: Some(config.airgroup_id as u64),
has_compressor: config.has_compressor,
..Default::default()
};
let gen_input = crate::io::recurser::GenCircomInput {
template_name: template.ejs_template(),
stark_infos: std::slice::from_ref(effective_si),
vadcop_info: config.global_info,
verifier_filenames: &verifier_filenames,
basic_verification_keys: config.verification_keys,
agg_verification_keys: &[],
publics: &[],
options: &gen_opts,
};
let circom_str = crate::io::recurser::gen_circom(&gen_input).context("gen_circom failed in recursive setup")?;
fs::write(&circom_out_path, &circom_str)?;
tracing::info!("Compiling {}...", name_filename);
let compile_status = std::process::Command::new(config.circom_exec)
.args([
"--O2",
"--r1cs",
"--prime",
"goldilocks",
"--c",
"--verbose",
"-l",
config.recurser_circuits_path,
"-l",
config.circuits_gl_path,
])
.arg(circom_out_path.to_str().unwrap())
.arg("-o")
.arg(build_dir_path.to_str().unwrap())
.output()
.context("Failed to execute circom compiler")?;
if !compile_status.status.success() {
let stderr = String::from_utf8_lossy(&compile_status.stderr);
bail!("Circom compilation failed for {}: {}", name_filename, stderr);
}
tracing::info!("Copying circom files...");
if dat_src.exists() {
fs::copy(&dat_src, &dat_dst)?;
}
let r1cs_data =
fs::read(&r1cs_path).with_context(|| format!("Failed to read R1CS file: {}", r1cs_path.display()))?;
plonk2pil::plonk2pil(&r1cs_data, type_compressor, &plonk_opts).context("plonk2pil failed in recursive setup")
};
let mut plonk_result = run_circom_and_plonk(config.stark_info)?;
const RECURSIVE_BITS_THRESHOLD: usize = 17;
if template == RecursiveTemplate::Recursive1 && !config.has_compressor {
if plonk_result.n_bits > RECURSIVE_BITS_THRESHOLD {
tracing::warn!(
"Recursive1 for air '{}' has n_bits={} > {} — compressor needed",
config.air_name,
plonk_result.n_bits,
RECURSIVE_BITS_THRESHOLD
);
return Err(anyhow::Error::new(NeedsCompressorError { n_bits: plonk_result.n_bits }));
}
if plonk_result.n_bits < RECURSIVE_BITS_THRESHOLD {
let current_n_queries = config
.stark_info
.get("starkStruct")
.and_then(|s| s.get("nQueries"))
.and_then(|v| v.as_u64())
.unwrap_or(0);
if current_n_queries > 0 {
let numer = (1u64 << (RECURSIVE_BITS_THRESHOLD - 1)) + (1u64 << 12);
let n_used = plonk_result.n_used as u64;
let min_queries = (numer * current_n_queries).div_ceil(n_used);
tracing::info!(
"Air '{}' recursive1: n_bits={}, n_used={}, nQueries={}, \
minimumQueriesRequired={}",
config.air_name,
plonk_result.n_bits,
plonk_result.n_used,
current_n_queries,
min_queries
);
if min_queries > current_n_queries {
tracing::info!(
"A2: adjusting nQueries for air '{}' recursive1: {} → {}",
config.air_name,
current_n_queries,
min_queries
);
let mut adjusted_si = config.stark_info.clone();
if let Some(ss) = adjusted_si.get_mut("starkStruct") {
if let Some(obj) = ss.as_object_mut() {
obj.insert("nQueries".to_string(), serde_json::json!(min_queries));
}
}
if let Some(si_path) = config.stark_info_path {
fs::write(si_path, crate::output::json::to_json_string(&adjusted_si)?)?;
tracing::info!("A2: wrote adjusted starkInfo (nQueries={}) to {:?}", min_queries, si_path);
}
tracing::info!(
"A2: recompiling recursive1 for air '{}' with nQueries={}",
config.air_name,
min_queries
);
plonk_result = run_circom_and_plonk(&adjusted_si)?;
}
}
}
}
if template == RecursiveTemplate::Recursive1
&& config.has_compressor
&& plonk_result.n_bits < RECURSIVE_BITS_THRESHOLD
{
tracing::warn!(
"Recursive1 for air '{}' (has compressor) packs to n_bits={} < {} (n_used={}); \
requesting a compressor nQueries bump",
config.air_name,
plonk_result.n_bits,
RECURSIVE_BITS_THRESHOLD,
plonk_result.n_used
);
return Err(anyhow::Error::new(RecursiveTooSmallError {
n_bits: plonk_result.n_bits,
n_used: plonk_result.n_used,
}));
}
let witness_lib_params = if config.defer_witness_lib {
Some((name_filename.clone(), files_dir.to_string_lossy().into_owned()))
} else {
witness_tracker.run_witness_library_generation(
config.build_dir,
files_dir.to_str().unwrap_or(""),
&name_filename,
template_str,
config.circom_helpers_dir,
);
None
};
let fixed_bin_path = build_dir_path.join(format!("{}.fixed.bin", name_filename));
let fixed_info: Vec<(String, Vec<u32>, Vec<u64>)> =
plonk_result.fixed_pols.iter().map(|fp| (fp.name.clone(), vec![fp.index as u32], fp.values.clone())).collect();
fixed_cols::write_fixed_pols_bin(
fixed_bin_path.to_str().unwrap(),
&plonk_result.airgroup_name,
&plonk_result.air_name,
1u64 << plonk_result.n_bits,
&fixed_info,
)?;
let pil_path = pil_dir.join(format!("{}.pil", name_filename));
fs::write(&pil_path, &plonk_result.pil_str)?;
let exec_path = files_dir.join(format!("{}.exec", template_str));
let exec_bytes: Vec<u8> = plonk_result.exec.iter().flat_map(|v| v.to_le_bytes()).collect();
fs::write(&exec_path, &exec_bytes)?;
let pilout_path = build_dir_path.join(format!("{}.pilout", name_filename));
compile_pil(
pil_path.to_str().unwrap(),
pilout_path.to_str().unwrap(),
config.std_pil_path,
config.recurser_pil_path,
)?;
let const_path = files_dir.join(format!("{}.const", template_str));
let plonk_n_rows = 1usize << plonk_result.n_bits;
let plonk_n_fixed = plonk_result.fixed_pols.len();
tracing::info!("Running starkSetup for recursive circuit...");
let starkinfo_path = files_dir.join(format!("{}.starkinfo.json", template_str));
let (setup_stark_info, setup_verifier_info, setup_expressions_info) =
if let Some((existing_si, existing_vi, existing_ei)) = config.existing_pil_info.as_ref() {
tracing::info!("Reusing existing starkSetup for {} (skipping pil_info)", template_str);
let stark_info_loaded = crate::types::stark_info::StarkInfo::from_json(existing_si)?;
{
let proxy = PilOutProxy::new(pilout_path.to_str().unwrap_or(""))
.map_err(|e| anyhow::anyhow!("Failed to load pilout for const file: {}", e))?;
if proxy.pilout.air_groups.is_empty() || proxy.pilout.air_groups[0].airs.is_empty() {
bail!("Pilout has no AIR groups: {}", pilout_path.display());
}
let air = &proxy.pilout.air_groups[0].airs[0];
let plonk_values =
fixed_cols::reorder_plonk_pols_for_pilout(&plonk_result.fixed_pols, &proxy.pilout.symbols, 0, 0);
fixed_cols::write_const_file(const_path.to_str().unwrap(), air, &plonk_values)?;
tracing::info!(
"Wrote {} const file (reused setup): {} cols, {} rows",
template_str,
air.fixed_cols.len(),
air.num_rows.unwrap_or(0)
);
}
fs::write(&starkinfo_path, serde_json::to_string_pretty(existing_si)?)?;
if template != RecursiveTemplate::Recursive1 {
fs::write(
files_dir.join(format!("{}.verifierinfo.json", template_str)),
serde_json::to_string_pretty(existing_vi)?,
)?;
fs::write(
files_dir.join(format!("{}.expressionsinfo.json", template_str)),
serde_json::to_string_pretty(existing_ei)?,
)?;
let vi_loaded = crate::types::stark_info::VerifierInfo::from_json(existing_vi)?;
let ei_loaded = crate::types::stark_info::ExpressionsInfo::from_json(existing_ei)?;
crate::io::bin_file::write_expressions_bin_file(
files_dir.join(format!("{}.bin", template_str)).to_str().unwrap(),
&stark_info_loaded,
&ei_loaded,
)?;
crate::io::bin_file::write_verifier_expressions_bin_file(
files_dir.join(format!("{}.verifier.bin", template_str)).to_str().unwrap(),
&stark_info_loaded,
&vi_loaded,
)?;
}
(Some(existing_si.clone()), Some(existing_vi.clone()), Some(existing_ei.clone()))
} else {
let pilout_path_str = pilout_path.to_str().unwrap_or("");
if !Path::new(pilout_path_str).exists() {
bail!("Pilout not found at {}. Cannot run starkSetup for recursive circuit.", pilout_path_str);
}
let proxy =
PilOutProxy::new(pilout_path_str).map_err(|e| anyhow::anyhow!("Failed to load pilout: {}", e))?;
let pilout = &proxy.pilout;
if pilout.air_groups.is_empty() || pilout.air_groups[0].airs.is_empty() {
bail!("Compiled pilout has no AIR groups: {}", pilout_path_str);
}
let air = &pilout.air_groups[0].airs[0];
let num_rows_air = air.num_rows.unwrap_or(0) as usize;
let n_bits_air = if num_rows_air > 0 { (num_rows_air as f64).log2() as usize } else { plonk_result.n_bits };
let make_recursive_settings = || {
let blowup = if template == RecursiveTemplate::Compressor { 2 } else { 3 };
crate::types::stark_struct::StarkSettings {
blowup_factor: Some(blowup),
folding_factor: Some(3),
final_degree: Some(5),
last_level_verification: None,
..Default::default()
}
};
let stark_struct = if let Some(ss_val) = config.stark_struct {
serde_json::from_value::<crate::types::stark_struct::StarkStruct>(ss_val.clone()).unwrap_or_else(|_| {
crate::types::stark_struct::generate_stark_struct(&make_recursive_settings(), n_bits_air)
})
} else {
crate::types::stark_struct::generate_stark_struct(&make_recursive_settings(), n_bits_air)
};
let pil_info_result = crate::pil::info::pil_info(pilout, 0, 0, &stark_struct, &Default::default());
let opening_points = crate::output::stark_info::collect_opening_points(&pil_info_result.setup);
let log_folding_factors = crate::output::stark_info::compute_log_folding_factors(&stark_struct);
let ev_map_len = pil_info_result.pil_code.ev_map.len();
let field_size = crate::types::security::goldilocks_safe_extension_field_size();
let regime = crate::types::security::regimes::DecodingRegime::Jbr;
let fri_config = crate::types::security::pcs::FriConfig {
field_size,
trace_length: 1u32 << stark_struct.n_bits,
rate: 1.0 / (1u64 << (stark_struct.n_bits_ext - stark_struct.n_bits)) as f64,
batch_size: ev_map_len.max(1) as u64,
batching: crate::types::security::pcs::Batching::Powers,
log_folding_factors,
max_grinding_bits_query: stark_struct.pow_bits as u64,
use_max_grinding_bits_query: true,
tree_arity: stark_struct.merkle_tree_arity as u64,
hash_size_bits: 256,
target_security_bits: 128,
regime,
};
let mut fri = crate::types::security::pcs::Fri::new(fri_config);
let override_q = stark_struct.n_queries as u64;
if config.stark_struct.is_some() {
let security_floor = fri.security_params().n_queries;
if fri.raise_n_queries(override_q) {
tracing::info!(
"Honoring nQueries override for {}: {} → {} (security floor {})",
template_str,
security_floor,
override_q,
security_floor
);
}
}
let starkinfo_output = crate::output::stark_info::build_starkinfo_output(
&pil_info_result.setup,
&stark_struct,
&pil_info_result.pil_code,
&opening_points,
&fri,
config.airgroup_id,
config.air_id,
&airgroup_pil_name,
pil_info_result.c_exp_id,
pil_info_result.fri_exp_id,
pil_info_result.q_deg,
);
let verifier_info_ref = &pil_info_result.pil_code.verifier_info;
let expressions_info_ref = &pil_info_result.pil_code.expressions_info;
let si_json = serde_json::to_value(&starkinfo_output)?;
fs::write(&starkinfo_path, crate::output::json::to_json_string(&starkinfo_output)?)?;
let stark_info_loaded = crate::types::stark_info::StarkInfo::from_json(&si_json)?;
if template != RecursiveTemplate::Recursive1 {
fs::write(
files_dir.join(format!("{}.verifierinfo.json", template_str)),
crate::output::json::to_json_string(verifier_info_ref)?,
)?;
fs::write(
files_dir.join(format!("{}.expressionsinfo.json", template_str)),
crate::output::json::to_json_string(expressions_info_ref)?,
)?;
let expressions_loaded = crate::types::stark_info::ExpressionsInfo::from(expressions_info_ref);
crate::io::bin_file::write_expressions_bin_file(
files_dir.join(format!("{}.bin", template_str)).to_str().unwrap(),
&stark_info_loaded,
&expressions_loaded,
)?;
let verifier_loaded = crate::types::stark_info::VerifierInfo::from(verifier_info_ref);
crate::io::bin_file::write_verifier_expressions_bin_file(
files_dir.join(format!("{}.verifier.bin", template_str)).to_str().unwrap(),
&stark_info_loaded,
&verifier_loaded,
)?;
}
{
let plonk_values =
fixed_cols::reorder_plonk_pols_for_pilout(&plonk_result.fixed_pols, &pilout.symbols, 0, 0);
fixed_cols::write_const_file(const_path.to_str().unwrap(), air, &plonk_values)?;
tracing::info!(
"Wrote {} const file: {} cols ({} from plonk + {} from pilout), {} rows",
template_str,
air.fixed_cols.len(),
plonk_n_fixed,
air.fixed_cols.len().saturating_sub(plonk_n_fixed),
plonk_n_rows
);
}
(
Some(si_json),
Some(serde_json::to_value(verifier_info_ref)?),
Some(serde_json::to_value(expressions_info_ref)?),
)
};
let verkey_json_path = files_dir.join(format!("{}.verkey.json", template_str));
let const_root = if const_path.exists() && starkinfo_path.exists() {
let root = bctree::compute_const_tree(
const_path.to_str().unwrap(),
starkinfo_path.to_str().unwrap(),
verkey_json_path.to_str().unwrap(),
);
let mut verkey_bin = Vec::with_capacity(32);
for &val in root.iter() {
verkey_bin.extend_from_slice(&val.to_le_bytes());
}
fs::write(files_dir.join(format!("{}.verkey.bin", template_str)), &verkey_bin)?;
root
} else {
bail!(
"Cannot compute const tree: const file ({}) or starkinfo ({}) missing",
const_path.display(),
starkinfo_path.display()
);
};
if template == RecursiveTemplate::Recursive1 {
let _ = fs::remove_file(&starkinfo_path);
}
if template == RecursiveTemplate::Recursive2 {
let r1_vks = config.verification_keys.first().cloned().unwrap_or_default();
let root_c_recursive2: Vec<serde_json::Value> =
const_root.iter().map(|&v| serde_json::Value::from(v)).collect();
let r1_vks_numeric: Vec<Vec<serde_json::Value>> = r1_vks
.iter()
.map(|air_vk| {
air_vk
.iter()
.map(|v| {
v.parse::<u64>()
.map(serde_json::Value::from)
.unwrap_or_else(|_| serde_json::Value::String(v.clone()))
})
.collect()
})
.collect();
let vks = serde_json::json!({
"rootCRecursives1": r1_vks_numeric,
"rootCRecursive2": root_c_recursive2,
});
fs::write(files_dir.join(format!("{}.vks.json", template_str)), serde_json::to_string_pretty(&vks)?)?;
if let (Some(ref si_val), Some(ref vi_val)) = (&setup_stark_info, &setup_verifier_info) {
let si_loaded = crate::types::stark_info::StarkInfo::from_json(si_val)?;
let vi_loaded = crate::types::stark_info::VerifierInfo::from_json(vi_val)?;
crate::output::verifier::write_verifier_rust_file(
files_dir.join(format!("{}.verifier.rs", template_str)).to_str().unwrap(),
&si_loaded,
&vi_loaded,
true, config.hash,
)?;
}
}
Ok(RecursiveSetupResult {
const_root,
pil_str: plonk_result.pil_str,
stark_info: setup_stark_info,
verifier_info: setup_verifier_info,
expressions_info: setup_expressions_info,
n_bits: plonk_result.n_bits,
n_used: plonk_result.n_used,
witness_lib_params,
})
}
fn resolve_names_and_paths(
config: &RecursiveSetupConfig<'_>,
) -> Result<(
String, // verifier_name
String, // name_filename
PathBuf, // files_dir
bool, // input_challenges
bool, // verkey_input
bool, // enable_input
)> {
let template = config.template;
let build_dir = PathBuf::from(config.build_dir);
match template {
RecursiveTemplate::Compressor => {
let verifier_name = format!("{}.verifier.circom", config.air_name);
let name_filename = format!("{}_{}", config.air_name, template.as_str());
let files_dir = build_dir
.join("provingKey")
.join(get_global_name(config.global_info))
.join(config.airgroup_name)
.join("airs")
.join(config.air_name)
.join(template.as_str());
Ok((verifier_name, name_filename, files_dir, true, false, false))
}
RecursiveTemplate::Recursive1 if !config.has_compressor => {
let verifier_name = format!("{}.verifier.circom", config.air_name);
let name_filename = format!("{}_{}", config.air_name, template.as_str());
let files_dir = build_dir
.join("provingKey")
.join(get_global_name(config.global_info))
.join(config.airgroup_name)
.join("airs")
.join(config.air_name)
.join(template.as_str());
Ok((verifier_name, name_filename, files_dir, true, false, false))
}
RecursiveTemplate::Recursive1 => {
let verifier_name = format!("{}_compressor.verifier.circom", config.air_name);
let name_filename = format!("{}_{}", config.air_name, template.as_str());
let files_dir = build_dir
.join("provingKey")
.join(get_global_name(config.global_info))
.join(config.airgroup_name)
.join("airs")
.join(config.air_name)
.join("recursive1");
Ok((verifier_name, name_filename, files_dir, false, false, false))
}
RecursiveTemplate::Recursive2 => {
let verifier_name = format!("{}_recursive2.verifier.circom", config.airgroup_name);
let name_filename = format!("{}_{}", config.airgroup_name, template.as_str());
let files_dir = build_dir
.join("provingKey")
.join(get_global_name(config.global_info))
.join(config.airgroup_name)
.join(template.as_str());
let n_airgroups =
config.global_info.get("air_groups").and_then(|v| v.as_array()).map(|a| a.len()).unwrap_or(1);
let n_airs_first = config
.global_info
.get("airs")
.and_then(|v| v.as_array())
.and_then(|a| a.first())
.and_then(|v| v.as_array())
.map(|a| a.len())
.unwrap_or(1);
let enable_input = n_airgroups > 1 || n_airs_first > 1;
Ok((verifier_name, name_filename, files_dir, false, true, enable_input))
}
}
}
fn get_global_name(global_info: &Value) -> String {
global_info.get("name").and_then(|v| v.as_str()).unwrap_or("pilout").to_string()
}
pub fn compile_pil(pil_path: &str, output_path: &str, std_pil_path: &str, recurser_pil_path: &str) -> Result<()> {
use crate::commands::compile_pil::{run_compile_pil, CompilePilOptions};
let opts = CompilePilOptions {
pil_path: pil_path.to_string(),
output_path: output_path.to_string(),
include_paths: vec![std_pil_path.to_string(), recurser_pil_path.to_string()],
fixed_dir: None,
fixed_to_file: false,
no_proto_fixed_data: false,
};
run_compile_pil(&opts)
}
fn resolve_pil2com_exec() -> Option<String> {
if let Ok(path) = std::env::var("PIL2C_EXEC") {
if Path::new(&path).is_file() {
return Some(path);
}
}
const CRATE_ROOT: &str = env!("CARGO_MANIFEST_DIR");
let baked = Path::new(CRATE_ROOT).join("node_modules/.bin/pil2com");
if baked.is_file() {
if let Ok(abs) = baked.canonicalize() {
return abs.to_str().map(|s| s.to_string());
}
}
let local_npm = Path::new("node_modules/.bin/pil2com");
if local_npm.is_file() {
if let Ok(abs) = local_npm.canonicalize() {
return abs.to_str().map(|s| s.to_string());
}
}
if let Ok(exe) = std::env::current_exe() {
let mut dir = exe.parent();
while let Some(d) = dir {
let candidate = d.join("node_modules/.bin/pil2com");
if candidate.is_file() {
if let Ok(abs) = candidate.canonicalize() {
return abs.to_str().map(|s| s.to_string());
}
}
dir = d.parent();
}
}
which::which("pil2com").ok().map(|p| p.to_string_lossy().into_owned())
}
pub(crate) fn ensure_pil2com_exec() -> Option<String> {
static PIL2COM_EXEC: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
PIL2COM_EXEC.get_or_init(|| resolve_pil2com_exec().or_else(|| bootstrapped_node_path(".bin/pil2com"))).clone()
}
fn bootstrapped_node_path(probe: &str) -> Option<String> {
let root = crate::proving_key::node_deps::ensure_node_deps(probe)?;
let path = root.join("node_modules").join(probe);
path.canonicalize().ok().map(|p| p.to_string_lossy().into_owned())
}
fn find_node_module_subpath(env_var: &str, package: &str, sub_path: &str) -> Option<String> {
if let Ok(v) = std::env::var(env_var) {
if !v.is_empty() {
return Some(v);
}
}
const CRATE_ROOT: &str = env!("CARGO_MANIFEST_DIR");
let baked = std::path::Path::new(CRATE_ROOT).join("node_modules").join(package).join(sub_path);
if baked.is_dir() {
if let Ok(abs) = baked.canonicalize() {
return Some(abs.to_string_lossy().into_owned());
}
}
let rel = PathBuf::from("node_modules").join(package).join(sub_path);
if rel.is_dir() {
if let Ok(abs) = rel.canonicalize() {
return Some(abs.to_string_lossy().into_owned());
}
}
if let Ok(exe) = std::env::current_exe() {
let mut dir = exe.parent();
while let Some(d) = dir {
let candidate = d.join("node_modules").join(package).join(sub_path);
if candidate.is_dir() {
if let Ok(abs) = candidate.canonicalize() {
return Some(abs.to_string_lossy().into_owned());
}
}
dir = d.parent();
}
}
None
}
pub(crate) fn ensure_node_module_subpath(env_var: &str, package: &str, sub_path: &str) -> String {
if let Some(p) = find_node_module_subpath(env_var, package, sub_path) {
return p;
}
let probe = format!("{package}/{sub_path}");
bootstrapped_node_path(&probe).unwrap_or_else(|| format!("node_modules/{probe}"))
}