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 serde::Serialize;
40use std::{fmt::Write, path::PathBuf};
41
42#[derive(Serialize)]
43pub struct Metadata {
44 pub prover_checksum: String,
45 pub prover_size: usize,
46 pub verifier_checksum: String,
47 pub verifier_size: usize,
48}
49
50#[derive(Parser, Debug)]
52pub struct LeoSynthesize {
53 #[clap(name = "NAME", help = "The name of the program to synthesize, e.g `helloworld.aleo`")]
54 pub(crate) program_name: String,
55 #[arg(short, long, help = "Use the local Leo project.")]
56 pub(crate) local: bool,
57 #[arg(short, long, help = "Skip functions that contain any of the given substrings")]
58 pub(crate) skip: Vec<String>,
59 #[clap(flatten)]
60 pub(crate) action: TransactionAction,
61 #[clap(flatten)]
62 pub(crate) env_override: EnvOptions,
63 #[clap(flatten)]
64 build_options: BuildOptions,
65}
66
67impl Command for LeoSynthesize {
68 type Input = Option<Package>;
69 type Output = ();
70
71 fn log_span(&self) -> Span {
72 tracing::span!(tracing::Level::INFO, "Leo")
73 }
74
75 fn prelude(&self, context: Context) -> Result<Self::Input> {
76 if self.local {
78 let package = LeoBuild {
79 env_override: self.env_override.clone(),
80 options: {
81 let mut options = self.build_options.clone();
82 options.no_cache = true;
83 options
84 },
85 }
86 .execute(context)?;
87 Ok(Some(package))
89 } else {
90 Ok(None)
91 }
92 }
93
94 fn apply(self, context: Context, input: Self::Input) -> Result<Self::Output> {
95 if self.action.broadcast {
97 println!(
98 "❌ `--broadcast` is not a valid option for `leo synthesize`. Please use `--save` and specify a valid directory."
99 );
100 return Ok(());
101 } else if self.action.print {
102 println!(
103 "❌ `--print` is not a valid option for `leo synthesize`. Please use `--save` and specify a valid directory."
104 );
105 return Ok(());
106 }
107
108 let network = get_network(&self.env_override.network)?;
110 match network {
112 NetworkName::TestnetV0 => handle_synthesize::<AleoTestnetV0>(self, context, network, input),
113 NetworkName::MainnetV0 => {
114 #[cfg(feature = "only_testnet")]
115 panic!("Mainnet chosen with only_testnet feature");
116 #[cfg(not(feature = "only_testnet"))]
117 handle_synthesize::<AleoV0>(self, context, network, input)
118 }
119 NetworkName::CanaryV0 => {
120 #[cfg(feature = "only_testnet")]
121 panic!("Canary chosen with only_testnet feature");
122 #[cfg(not(feature = "only_testnet"))]
123 handle_synthesize::<AleoCanaryV0>(self, context, network, input)
124 }
125 }
126 }
127}
128
129fn handle_synthesize<A: Aleo>(
131 command: LeoSynthesize,
132 context: Context,
133 network: NetworkName,
134 package: Option<Package>,
135) -> Result<<LeoSynthesize as Command>::Output> {
136 let endpoint = get_endpoint(&command.env_override.endpoint)?;
138
139 let program_id = ProgramID::<A::Network>::from_str(&command.program_name)
141 .map_err(|e| CliError::custom(format!("Failed to parse program name: {e}")))?;
142
143 let programs = if let Some(package) = &package {
146 let build_directory = package.build_directory();
148 let imports_directory = package.imports_directory();
149 let source_directory = package.source_directory();
150 package
152 .programs
153 .iter()
154 .clone()
155 .map(|program| {
156 let program_id = ProgramID::<A::Network>::from_str(&format!("{}.aleo", program.name))
157 .map_err(|e| CliError::custom(format!("Failed to parse program ID: {e}")))?;
158 match &program.data {
159 ProgramData::Bytecode(bytecode) => Ok((program_id, bytecode.to_string(), program.edition)),
160 ProgramData::SourcePath { source, .. } => {
161 let bytecode_path = if source.as_path() == source_directory.join("main.leo") {
163 build_directory.join("main.aleo")
164 } else {
165 imports_directory.join(format!("{}.aleo", program.name))
166 };
167 let bytecode = std::fs::read_to_string(&bytecode_path).map_err(|e| {
169 CliError::custom(format!("Failed to read bytecode at {}: {e}", bytecode_path.display()))
170 })?;
171 Ok((program_id, bytecode, program.edition))
173 }
174 }
175 })
176 .collect::<Result<Vec<_>>>()?
177 } else {
178 Vec::new()
179 };
180
181 let mut programs = programs
183 .into_iter()
184 .map(|(_, bytecode, edition)| {
185 let program = snarkvm::prelude::Program::<A::Network>::from_str(&bytecode)
187 .map_err(|e| CliError::custom(format!("Failed to parse program: {e}")))?;
188 Ok((program, edition))
190 })
191 .collect::<Result<Vec<_>>>()?;
192
193 let is_local = programs.iter().any(|(program, _)| program.id() == &program_id);
195
196 let rng = &mut rand::thread_rng();
198
199 let vm = VM::from(ConsensusStore::<A::Network, ConsensusMemory<A::Network>>::open(StorageMode::Production)?)?;
201
202 if !is_local {
205 println!("⬇️ Downloading {program_id} and its dependencies from {endpoint}...");
206 programs = load_latest_programs_from_network(&context, program_id, network, &endpoint)?;
207 };
208
209 println!("\n➕ Adding programs to the VM in the following order:");
211 let programs_and_editions = programs
212 .into_iter()
213 .map(|(program, edition)| {
214 let is_default = edition.is_none();
216 let edition = edition.unwrap_or(1);
217 let id = program.id().to_string();
219 match id == "credits.aleo" {
221 true => println!(" - {id} (already included)"),
222 false => match is_default {
223 true => println!(" - {id} (defaulting to edition {edition})"),
224 false => println!(" - {id} (edition: {edition})"),
225 },
226 }
227 (program, edition)
228 })
229 .collect::<Vec<_>>();
230 vm.process().write().add_programs_with_editions(&programs_and_editions)?;
231
232 let stack = vm.process().read().get_stack(program_id)?;
234 let edition = *stack.program_edition();
235 let function_ids = stack
236 .program()
237 .functions()
238 .keys()
239 .filter(|id| !command.skip.iter().any(|substring| id.to_string().contains(substring)))
240 .collect::<Vec<_>>();
241
242 let hash = |bytes: &[u8]| -> anyhow::Result<String> {
244 let digest = sha256(bytes);
245 let mut hex = String::new();
246 for byte in digest {
247 write!(&mut hex, "{byte:02x}")?;
248 }
249 Ok(hex)
250 };
251
252 println!("\n🌱 Synthesizing the following keys in {program_id}:");
253 for id in &function_ids {
254 println!(" - {id}");
255 }
256
257 for function_id in function_ids {
258 stack.synthesize_key::<A, _>(function_id, rng)?;
259 let proving_key = stack.get_proving_key(function_id)?;
260 let verifying_key = stack.get_verifying_key(function_id)?;
261
262 println!("\n🔑 Synthesized keys for {program_id}/{function_id} (edition {edition})");
263 println!("ℹ️ Circuit Information:");
264 println!(" - Public Inputs: {}", verifying_key.circuit_info.num_public_inputs);
265 println!(" - Variables: {}", verifying_key.circuit_info.num_public_and_private_variables);
266 println!(" - Constraints: {}", verifying_key.circuit_info.num_constraints);
267 println!(" - Non-Zero Entries in A: {}", verifying_key.circuit_info.num_non_zero_a);
268 println!(" - Non-Zero Entries in B: {}", verifying_key.circuit_info.num_non_zero_b);
269 println!(" - Non-Zero Entries in C: {}", verifying_key.circuit_info.num_non_zero_c);
270 println!(" - Circuit ID: {}", verifying_key.id);
271
272 let prover_bytes = proving_key.to_bytes_le()?;
274 let verifier_bytes = verifying_key.to_bytes_le()?;
275 let prover_checksum = hash(&prover_bytes)?;
276 let verifier_checksum = hash(&verifier_bytes)?;
277
278 let metadata = Metadata {
280 prover_checksum,
281 prover_size: prover_bytes.len(),
282 verifier_checksum,
283 verifier_size: verifier_bytes.len(),
284 };
285 let metadata_pretty = serde_json::to_string_pretty(&metadata)
286 .map_err(|e| CliError::custom(format!("Failed to serialize metadata: {e}")))?;
287
288 let write_to_file = |path: PathBuf, data: &[u8]| -> Result<()> {
290 std::fs::write(path, data).map_err(|e| CliError::custom(format!("Failed to write to file: {e}")))?;
291 Ok(())
292 };
293
294 if let Some(path) = &command.action.save {
298 std::fs::create_dir_all(path).map_err(|e| CliError::custom(format!("Failed to create directory: {e}")))?;
300 let timestamp = chrono::Utc::now().timestamp();
302 let edition = if command.local { "local".to_string() } else { edition.to_string() };
304 let prefix = format!("{network}.{program_id}.{function_id}.{edition}");
306 let prover_file_path = PathBuf::from(path).join(format!("{prefix}.prover.{timestamp}"));
308 let verifier_file_path = PathBuf::from(path).join(format!("{prefix}.verifier.{timestamp}"));
309 let metadata_file_path = PathBuf::from(path)
310 .join(format!("{network}.{program_id}.{function_id}.{edition}.metadata.{timestamp}"));
311 println!(
313 "💾 Saving proving key, verifying key, and metadata to: {}/{network}.{program_id}.{function_id}.{edition}.prover|verifier|metadata.{timestamp}",
314 metadata_file_path.parent().unwrap().display()
315 );
316 write_to_file(prover_file_path, &prover_bytes)?;
318 write_to_file(verifier_file_path, &verifier_bytes)?;
319 write_to_file(metadata_file_path, metadata_pretty.as_bytes())?;
320 }
321 }
322
323 Ok(())
324}