1use super::*;
18
19use leo_ast::NetworkName;
20use leo_package::{Package, ProgramData};
21
22use aleo_std::StorageMode;
23
24#[cfg(not(feature = "only_testnet"))]
25use snarkvm::circuit::{AleoCanaryV0, AleoV0};
26use snarkvm::{
27 algorithms::crypto_hash::sha256,
28 circuit::{Aleo, AleoTestnetV0},
29 prelude::{
30 ProgramID,
31 ToBytes,
32 VM,
33 store::{ConsensusStore, helpers::memory::ConsensusMemory},
34 },
35 synthesizer::program::StackTrait,
36};
37
38use clap::Parser;
39use std::{fmt::Write, path::PathBuf};
40
41#[derive(Parser, Debug)]
43pub struct LeoSynthesize {
44 #[clap(name = "NAME", help = "The name of the program to synthesize, e.g `helloworld.aleo`")]
45 pub(crate) program_name: String,
46 #[arg(short, long, help = "Use the local Leo project.")]
47 pub(crate) local: bool,
48 #[arg(short, long, help = "Skip functions that contain any of the given substrings")]
49 pub(crate) skip: Vec<String>,
50 #[arg(long, help = "Save the synthesized keys to the provided directory.")]
51 pub(crate) save: Option<String>,
52 #[clap(flatten)]
53 pub(crate) env_override: EnvOptions,
54}
55
56impl Command for LeoSynthesize {
57 type Input = Option<Package>;
58 type Output = SynthesizeOutput;
59
60 fn log_span(&self) -> Span {
61 tracing::span!(tracing::Level::INFO, "Leo")
62 }
63
64 fn prelude(&self, context: Context) -> Result<Self::Input> {
65 if self.local {
67 let package = LeoBuild {
68 env_override: self.env_override.clone(),
69 options: BuildOptions { no_cache: true, ..Default::default() },
71 rename: None,
72 }
73 .execute(context)?;
74 Ok(Some(package))
76 } else {
77 Ok(None)
78 }
79 }
80
81 fn apply(self, context: Context, input: Self::Input) -> Result<Self::Output> {
82 let network = get_network(&self.env_override.network)?;
84 match network {
86 NetworkName::TestnetV0 => handle_synthesize::<AleoTestnetV0>(self, context, network, input),
87 NetworkName::MainnetV0 => {
88 #[cfg(feature = "only_testnet")]
89 panic!("Mainnet chosen with only_testnet feature");
90 #[cfg(not(feature = "only_testnet"))]
91 handle_synthesize::<AleoV0>(self, context, network, input)
92 }
93 NetworkName::CanaryV0 => {
94 #[cfg(feature = "only_testnet")]
95 panic!("Canary chosen with only_testnet feature");
96 #[cfg(not(feature = "only_testnet"))]
97 handle_synthesize::<AleoCanaryV0>(self, context, network, input)
98 }
99 }
100 }
101}
102
103fn handle_synthesize<A: Aleo>(
105 command: LeoSynthesize,
106 context: Context,
107 network: NetworkName,
108 package: Option<Package>,
109) -> Result<<LeoSynthesize as Command>::Output> {
110 let endpoint = get_endpoint(&command.env_override.endpoint)?;
112
113 let program_id = ProgramID::<A::Network>::from_str(&command.program_name)
115 .map_err(|e| crate::errors::custom(format!("Failed to parse program name: {e}")))?;
116
117 let programs = if let Some(package) = &package {
120 package
122 .compilation_units
123 .iter()
124 .clone()
125 .filter(|unit| !unit.kind.is_library())
126 .map(|unit| {
127 let program_id = ProgramID::<A::Network>::from_str(&format!("{}", unit.name))
128 .map_err(|e| crate::errors::custom(format!("Failed to parse program ID: {e}")))?;
129 match &unit.data {
130 ProgramData::Bytecode(bytecode) => Ok((program_id, bytecode.to_string(), unit.edition)),
131 ProgramData::SourcePath { .. } => {
132 let bytecode_path = package.unit_bytecode_path(&unit.name.to_string());
134 let bytecode = std::fs::read_to_string(&bytecode_path).map_err(|e| {
136 crate::errors::custom(format!(
137 "Failed to read bytecode at {}: {e}",
138 bytecode_path.display()
139 ))
140 })?;
141 Ok((program_id, bytecode, unit.edition))
143 }
144 }
145 })
146 .collect::<Result<Vec<_>>>()?
147 } else {
148 Vec::new()
149 };
150
151 let mut programs = programs
153 .into_iter()
154 .map(|(_, bytecode, edition)| {
155 let program = snarkvm::prelude::Program::<A::Network>::from_str(&bytecode)
157 .map_err(|e| crate::errors::custom(format!("Failed to parse program: {e}")))?;
158 Ok((program, edition))
160 })
161 .collect::<Result<Vec<_>>>()?;
162
163 let is_local = programs.iter().any(|(program, _)| program.id() == &program_id);
165
166 let rng = &mut rand::rng();
168
169 let vm = VM::from(ConsensusStore::<A::Network, ConsensusMemory<A::Network>>::open(StorageMode::Production)?)?;
171
172 if !is_local {
175 println!("⬇️ Downloading {program_id} and its dependencies from {endpoint}...");
176 programs = load_latest_programs_from_network(
177 &context,
178 program_id,
179 network,
180 &endpoint,
181 command.env_override.network_retries,
182 )?;
183 };
184
185 println!("\n➕ Adding programs to the VM in the following order:");
187 let programs_and_editions = programs
188 .into_iter()
189 .map(|(program, edition)| {
190 print_program_source(&program.id().to_string(), edition);
191 let edition = edition.unwrap_or(LOCAL_PROGRAM_DEFAULT_EDITION);
192 (program, edition)
193 })
194 .collect::<Vec<_>>();
195 vm.process().lock().add_programs_with_editions(&programs_and_editions)?;
196
197 let stack = vm.process().get_stack(program_id)?;
199 let edition = *stack.program_edition();
200 let function_ids = stack
201 .program()
202 .functions()
203 .keys()
204 .filter(|id| !command.skip.iter().any(|substring| id.to_string().contains(substring)))
205 .collect::<Vec<_>>();
206
207 let hash = |bytes: &[u8]| -> anyhow::Result<String> {
209 let digest = sha256(bytes);
210 let mut hex = String::new();
211 for byte in digest {
212 write!(&mut hex, "{byte:02x}")?;
213 }
214 Ok(hex)
215 };
216
217 let record_names: Vec<_> = stack.program().records().keys().cloned().collect();
219
220 println!("\n🌱 Synthesizing the following keys in {program_id}:");
221 for id in &function_ids {
222 println!(" - {id} (function)");
223 }
224 for name in &record_names {
225 println!(" - {name} (record translation)");
226 }
227
228 let mut synthesized_functions = Vec::new();
229
230 let mut process_key = |name: &str, label: &str| -> Result<()> {
233 let name_id = snarkvm::prelude::Identifier::<A::Network>::from_str(name)?;
234 let proving_key = stack.get_proving_key(&name_id)?;
235 let verifying_key = stack.get_verifying_key(&name_id)?;
236
237 println!("\n🔑 Synthesized {label} for {program_id}/{name} (edition {edition})");
238 println!("ℹ️ Circuit Information:");
239 println!(" - Public Inputs: {}", verifying_key.circuit_info.num_public_inputs);
240 println!(" - Variables: {}", verifying_key.num_variables());
241 println!(" - Constraints: {}", verifying_key.circuit_info.num_constraints);
242 println!(" - Non-Zero Entries in A: {}", verifying_key.circuit_info.num_non_zero_a);
243 println!(" - Non-Zero Entries in B: {}", verifying_key.circuit_info.num_non_zero_b);
244 println!(" - Non-Zero Entries in C: {}", verifying_key.circuit_info.num_non_zero_c);
245 println!(" - Circuit ID: {}", verifying_key.id);
246
247 let prover_bytes = proving_key.to_bytes_le()?;
248 let verifier_bytes = verifying_key.to_bytes_le()?;
249 let prover_checksum = hash(&prover_bytes)?;
250 let verifier_checksum = hash(&verifier_bytes)?;
251
252 let metadata = Metadata {
253 prover_checksum,
254 prover_size: prover_bytes.len(),
255 verifier_checksum,
256 verifier_size: verifier_bytes.len(),
257 };
258 let metadata_pretty = serde_json::to_string_pretty(&metadata)
259 .map_err(|e| crate::errors::custom(format!("Failed to serialize metadata: {e}")))?;
260
261 let circuit_info = CircuitInfo {
262 num_public_inputs: verifying_key.circuit_info.num_public_inputs as u64,
263 num_variables: verifying_key.num_variables(),
264 num_constraints: verifying_key.circuit_info.num_constraints as u64,
265 num_non_zero_a: verifying_key.circuit_info.num_non_zero_a as u64,
266 num_non_zero_b: verifying_key.circuit_info.num_non_zero_b as u64,
267 num_non_zero_c: verifying_key.circuit_info.num_non_zero_c as u64,
268 circuit_id: verifying_key.id.to_string(),
269 };
270
271 synthesized_functions.push(SynthesizedFunction {
272 name: name.to_string(),
273 circuit_info,
274 metadata: metadata.clone(),
275 });
276
277 if let Some(path) = &command.save {
278 std::fs::create_dir_all(path)
279 .map_err(|e| crate::errors::custom(format!("Failed to create directory: {e}")))?;
280 let timestamp = chrono::Utc::now().timestamp();
281 let edition_str = if command.local { "local".to_string() } else { edition.to_string() };
282 let prefix = format!("{network}.{program_id}.{name}.{edition_str}");
283 let prover_file_path = PathBuf::from(path).join(format!("{prefix}.prover.{timestamp}"));
284 let verifier_file_path = PathBuf::from(path).join(format!("{prefix}.verifier.{timestamp}"));
285 let metadata_file_path = PathBuf::from(path).join(format!("{prefix}.metadata.{timestamp}"));
286 println!(
287 "💾 Saving {label} to: {}/{prefix}.prover|verifier|metadata.{timestamp}",
288 metadata_file_path.parent().unwrap().display()
289 );
290 std::fs::write(&prover_file_path, &prover_bytes)
291 .map_err(|e| crate::errors::custom(format!("Failed to write to file: {e}")))?;
292 std::fs::write(&verifier_file_path, &verifier_bytes)
293 .map_err(|e| crate::errors::custom(format!("Failed to write to file: {e}")))?;
294 std::fs::write(&metadata_file_path, metadata_pretty.as_bytes())
295 .map_err(|e| crate::errors::custom(format!("Failed to write to file: {e}")))?;
296 }
297
298 Ok(())
299 };
300
301 for function_id in function_ids {
303 stack.synthesize_key::<A, _>(function_id, rng)?;
304 process_key(&function_id.to_string(), "keys")?;
305 }
306
307 for record_name in &record_names {
309 stack.synthesize_translation_key::<A, _>(record_name, rng)?;
310 process_key(&record_name.to_string(), "translation key")?;
311 }
312
313 Ok(SynthesizeOutput { program: program_id.to_string(), functions: synthesized_functions })
314}