use super::*;
use leo_ast::NetworkName;
use leo_package::{Package, ProgramData};
use aleo_std::StorageMode;
#[cfg(not(feature = "only_testnet"))]
use snarkvm::circuit::{AleoCanaryV0, AleoV0};
use snarkvm::{
algorithms::crypto_hash::sha256,
circuit::{Aleo, AleoTestnetV0},
prelude::{
ProgramID,
ToBytes,
VM,
store::{ConsensusStore, helpers::memory::ConsensusMemory},
},
synthesizer::program::StackTrait,
};
use clap::Parser;
use std::{fmt::Write, path::PathBuf};
#[derive(Parser, Debug)]
pub struct LeoSynthesize {
#[clap(name = "NAME", help = "The name of the program to synthesize, e.g `helloworld.aleo`")]
pub(crate) program_name: String,
#[arg(short, long, help = "Use the local Leo project.")]
pub(crate) local: bool,
#[arg(short, long, help = "Skip functions that contain any of the given substrings")]
pub(crate) skip: Vec<String>,
#[arg(long, help = "Save the synthesized keys to the provided directory.")]
pub(crate) save: Option<String>,
#[clap(flatten)]
pub(crate) env_override: EnvOptions,
}
impl Command for LeoSynthesize {
type Input = Option<Package>;
type Output = SynthesizeOutput;
fn log_span(&self) -> Span {
tracing::span!(tracing::Level::INFO, "Leo")
}
fn prelude(&self, context: Context) -> Result<Self::Input> {
if self.local {
let package = LeoBuild {
env_override: self.env_override.clone(),
options: BuildOptions { no_cache: true, ..Default::default() },
rename: None,
}
.execute(context)?;
Ok(Some(package))
} else {
Ok(None)
}
}
fn apply(self, context: Context, input: Self::Input) -> Result<Self::Output> {
let network = get_network(&self.env_override.network)?;
match network {
NetworkName::TestnetV0 => handle_synthesize::<AleoTestnetV0>(self, context, network, input),
NetworkName::MainnetV0 => {
#[cfg(feature = "only_testnet")]
panic!("Mainnet chosen with only_testnet feature");
#[cfg(not(feature = "only_testnet"))]
handle_synthesize::<AleoV0>(self, context, network, input)
}
NetworkName::CanaryV0 => {
#[cfg(feature = "only_testnet")]
panic!("Canary chosen with only_testnet feature");
#[cfg(not(feature = "only_testnet"))]
handle_synthesize::<AleoCanaryV0>(self, context, network, input)
}
}
}
}
fn handle_synthesize<A: Aleo>(
command: LeoSynthesize,
context: Context,
network: NetworkName,
package: Option<Package>,
) -> Result<<LeoSynthesize as Command>::Output> {
let endpoint = get_endpoint(&command.env_override.endpoint)?;
let program_id = ProgramID::<A::Network>::from_str(&command.program_name)
.map_err(|e| crate::errors::custom(format!("Failed to parse program name: {e}")))?;
let programs = if let Some(package) = &package {
package
.compilation_units
.iter()
.clone()
.filter(|unit| !unit.kind.is_library())
.map(|unit| {
let program_id = ProgramID::<A::Network>::from_str(&format!("{}", unit.name))
.map_err(|e| crate::errors::custom(format!("Failed to parse program ID: {e}")))?;
match &unit.data {
ProgramData::Bytecode(bytecode) => Ok((program_id, bytecode.to_string(), unit.edition)),
ProgramData::SourcePath { .. } => {
let bytecode_path = package.unit_bytecode_path(&unit.name.to_string());
let bytecode = std::fs::read_to_string(&bytecode_path).map_err(|e| {
crate::errors::custom(format!(
"Failed to read bytecode at {}: {e}",
bytecode_path.display()
))
})?;
Ok((program_id, bytecode, unit.edition))
}
}
})
.collect::<Result<Vec<_>>>()?
} else {
Vec::new()
};
let mut programs = programs
.into_iter()
.map(|(_, bytecode, edition)| {
let program = snarkvm::prelude::Program::<A::Network>::from_str(&bytecode)
.map_err(|e| crate::errors::custom(format!("Failed to parse program: {e}")))?;
Ok((program, edition))
})
.collect::<Result<Vec<_>>>()?;
let is_local = programs.iter().any(|(program, _)| program.id() == &program_id);
let rng = &mut rand::rng();
let vm = VM::from(ConsensusStore::<A::Network, ConsensusMemory<A::Network>>::open(StorageMode::Production)?)?;
if !is_local {
println!("âŦī¸ Downloading {program_id} and its dependencies from {endpoint}...");
programs = load_latest_programs_from_network(
&context,
program_id,
network,
&endpoint,
command.env_override.network_retries,
)?;
};
println!("\nâ Adding programs to the VM in the following order:");
let programs_and_editions = programs
.into_iter()
.map(|(program, edition)| {
print_program_source(&program.id().to_string(), edition);
let edition = edition.unwrap_or(LOCAL_PROGRAM_DEFAULT_EDITION);
(program, edition)
})
.collect::<Vec<_>>();
vm.process().lock().add_programs_with_editions(&programs_and_editions)?;
let stack = vm.process().get_stack(program_id)?;
let edition = *stack.program_edition();
let function_ids = stack
.program()
.functions()
.keys()
.filter(|id| !command.skip.iter().any(|substring| id.to_string().contains(substring)))
.collect::<Vec<_>>();
let hash = |bytes: &[u8]| -> anyhow::Result<String> {
let digest = sha256(bytes);
let mut hex = String::new();
for byte in digest {
write!(&mut hex, "{byte:02x}")?;
}
Ok(hex)
};
let record_names: Vec<_> = stack.program().records().keys().cloned().collect();
println!("\nđą Synthesizing the following keys in {program_id}:");
for id in &function_ids {
println!(" - {id} (function)");
}
for name in &record_names {
println!(" - {name} (record translation)");
}
let mut synthesized_functions = Vec::new();
let mut process_key = |name: &str, label: &str| -> Result<()> {
let name_id = snarkvm::prelude::Identifier::<A::Network>::from_str(name)?;
let proving_key = stack.get_proving_key(&name_id)?;
let verifying_key = stack.get_verifying_key(&name_id)?;
println!("\nđ Synthesized {label} for {program_id}/{name} (edition {edition})");
println!("âšī¸ Circuit Information:");
println!(" - Public Inputs: {}", verifying_key.circuit_info.num_public_inputs);
println!(" - Variables: {}", verifying_key.num_variables());
println!(" - Constraints: {}", verifying_key.circuit_info.num_constraints);
println!(" - Non-Zero Entries in A: {}", verifying_key.circuit_info.num_non_zero_a);
println!(" - Non-Zero Entries in B: {}", verifying_key.circuit_info.num_non_zero_b);
println!(" - Non-Zero Entries in C: {}", verifying_key.circuit_info.num_non_zero_c);
println!(" - Circuit ID: {}", verifying_key.id);
let prover_bytes = proving_key.to_bytes_le()?;
let verifier_bytes = verifying_key.to_bytes_le()?;
let prover_checksum = hash(&prover_bytes)?;
let verifier_checksum = hash(&verifier_bytes)?;
let metadata = Metadata {
prover_checksum,
prover_size: prover_bytes.len(),
verifier_checksum,
verifier_size: verifier_bytes.len(),
};
let metadata_pretty = serde_json::to_string_pretty(&metadata)
.map_err(|e| crate::errors::custom(format!("Failed to serialize metadata: {e}")))?;
let circuit_info = CircuitInfo {
num_public_inputs: verifying_key.circuit_info.num_public_inputs as u64,
num_variables: verifying_key.num_variables(),
num_constraints: verifying_key.circuit_info.num_constraints as u64,
num_non_zero_a: verifying_key.circuit_info.num_non_zero_a as u64,
num_non_zero_b: verifying_key.circuit_info.num_non_zero_b as u64,
num_non_zero_c: verifying_key.circuit_info.num_non_zero_c as u64,
circuit_id: verifying_key.id.to_string(),
};
synthesized_functions.push(SynthesizedFunction {
name: name.to_string(),
circuit_info,
metadata: metadata.clone(),
});
if let Some(path) = &command.save {
std::fs::create_dir_all(path)
.map_err(|e| crate::errors::custom(format!("Failed to create directory: {e}")))?;
let timestamp = chrono::Utc::now().timestamp();
let edition_str = if command.local { "local".to_string() } else { edition.to_string() };
let prefix = format!("{network}.{program_id}.{name}.{edition_str}");
let prover_file_path = PathBuf::from(path).join(format!("{prefix}.prover.{timestamp}"));
let verifier_file_path = PathBuf::from(path).join(format!("{prefix}.verifier.{timestamp}"));
let metadata_file_path = PathBuf::from(path).join(format!("{prefix}.metadata.{timestamp}"));
println!(
"đž Saving {label} to: {}/{prefix}.prover|verifier|metadata.{timestamp}",
metadata_file_path.parent().unwrap().display()
);
std::fs::write(&prover_file_path, &prover_bytes)
.map_err(|e| crate::errors::custom(format!("Failed to write to file: {e}")))?;
std::fs::write(&verifier_file_path, &verifier_bytes)
.map_err(|e| crate::errors::custom(format!("Failed to write to file: {e}")))?;
std::fs::write(&metadata_file_path, metadata_pretty.as_bytes())
.map_err(|e| crate::errors::custom(format!("Failed to write to file: {e}")))?;
}
Ok(())
};
for function_id in function_ids {
stack.synthesize_key::<A, _>(function_id, rng)?;
process_key(&function_id.to_string(), "keys")?;
}
for record_name in &record_names {
stack.synthesize_translation_key::<A, _>(record_name, rng)?;
process_key(&record_name.to_string(), "translation key")?;
}
Ok(SynthesizeOutput { program: program_id.to_string(), functions: synthesized_functions })
}