/ synthesizer / process / src / stack / deploy.rs
deploy.rs
  1  // Copyright (c) 2025 ADnet Contributors
  2  // This file is part of the AlphaVM library.
  3  
  4  // Licensed under the Apache License, Version 2.0 (the "License");
  5  // you may not use this file except in compliance with the License.
  6  // You may obtain a copy of the License at:
  7  
  8  // http://www.apache.org/licenses/LICENSE-2.0
  9  
 10  // Unless required by applicable law or agreed to in writing, software
 11  // distributed under the License is distributed on an "AS IS" BASIS,
 12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  // See the License for the specific language governing permissions and
 14  // limitations under the License.
 15  
 16  use super::*;
 17  
 18  use rand::{SeedableRng, rngs::StdRng};
 19  
 20  impl<N: Network> Stack<N> {
 21      /// Deploys the given program ID, if it does not exist.
 22      #[inline]
 23      pub fn deploy<A: circuit::Aleo<Network = N>, R: Rng + CryptoRng>(&self, rng: &mut R) -> Result<Deployment<N>> {
 24          let timer = timer!("Stack::deploy");
 25  
 26          // Ensure the program contains functions.
 27          ensure!(!self.program.functions().is_empty(), "Program '{}' has no functions", self.program.id());
 28  
 29          // Initialize a vector for the verifying keys and certificates.
 30          let mut verifying_keys = Vec::with_capacity(self.program.functions().len());
 31  
 32          for function_name in self.program.functions().keys() {
 33              // Synthesize the proving and verifying key.
 34              self.synthesize_key::<A, R>(function_name, rng)?;
 35              lap!(timer, "Synthesize key for {function_name}");
 36  
 37              // Retrieve the proving key.
 38              let proving_key = self.get_proving_key(function_name)?;
 39              // Retrieve the verifying key.
 40              let verifying_key = self.get_verifying_key(function_name)?;
 41              lap!(timer, "Retrieve the keys for {function_name}");
 42  
 43              // Certify the circuit.
 44              let certificate = Certificate::certify(&function_name.to_string(), &proving_key, &verifying_key)?;
 45              lap!(timer, "Certify the circuit");
 46  
 47              // Add the verifying key and certificate to the bundle.
 48              verifying_keys.push((*function_name, (verifying_key, certificate)));
 49          }
 50  
 51          finish!(timer);
 52  
 53          // Return the deployment.
 54          Deployment::new(*self.program_edition, self.program.clone(), verifying_keys, None, None)
 55      }
 56  
 57      /// Checks each function in the program on the given verifying key and certificate.
 58      #[inline]
 59      pub fn verify_deployment<A: circuit::Aleo<Network = N>, R: Rng + CryptoRng>(
 60          &self,
 61          _consensus_version: ConsensusVersion,
 62          deployment: &Deployment<N>,
 63          rng: &mut R,
 64      ) -> Result<()> {
 65          let timer = timer!("Stack::verify_deployment");
 66  
 67          // NOTE: As developer, you will likely still want to confirm that your
 68          // deployment is within R1CS constraint and variable limits using
 69          // targeted and parallelized synthesis.
 70          if cfg!(all(feature = "dev_skip_checks", feature = "test_consensus_heights")) {
 71              return Ok(());
 72          }
 73  
 74          // Sanity Checks //
 75  
 76          // Ensure the deployment is ordered.
 77          deployment.check_is_ordered()?;
 78  
 79          // Ensure the program in the stack and deployment matches.
 80          ensure!(&self.program == deployment.program(), "The stack program does not match the deployment program");
 81          // If the deployment contains a checksum, ensure it matches the one computed by the stack.
 82          if let Some(program_checksum) = deployment.program_checksum() {
 83              ensure!(
 84                  program_checksum == self.program_checksum,
 85                  "The deployment checksum does not match the stack checksum"
 86              );
 87          }
 88  
 89          // Check Verifying Keys //
 90  
 91          // Get the program ID.
 92          let program_id = self.program.id();
 93  
 94          // Check that the number of combined variables does not exceed the deployment limit.
 95          ensure!(deployment.num_combined_variables()? <= N::MAX_DEPLOYMENT_VARIABLES);
 96          // Check that the number of combined constraints does not exceed the deployment limit.
 97          ensure!(deployment.num_combined_constraints()? <= N::MAX_DEPLOYMENT_CONSTRAINTS);
 98  
 99          // Construct the call stacks and assignments used to verify the certificates.
100          let mut call_stacks = Vec::with_capacity(deployment.verifying_keys().len());
101  
102          // Sample a dummy `root_tvk` for circuit synthesis.
103          let root_tvk = None;
104          // Sample a dummy `caller` for circuit synthesis.
105          let caller = None;
106  
107          // Check that the number of functions matches the number of verifying keys.
108          ensure!(
109              deployment.program().functions().len() == deployment.verifying_keys().len(),
110              "The number of functions in the program does not match the number of verifying keys"
111          );
112  
113          #[cfg(not(any(test, feature = "test")))]
114          // Skip the certificate verification if the consensus version is before ConsensusVersion::V8.
115          // Circuit synthesis was changed in a backwards incompatible way in ConsensusVersion::V8.
116          if (ConsensusVersion::V1..=ConsensusVersion::V7).contains(&_consensus_version) {
117              finish!(timer);
118              return Ok(());
119          }
120  
121          // Create a seeded rng to use for input value and sub-stack generation.
122          // This is needed to ensure that the verification results of deployments are consistent across all parties,
123          // because currently there is a possible flakiness due to overflows in Field to Scalar casting.
124          let seed = u64::from_bytes_le(&deployment.to_deployment_id()?.to_bytes_le()?[0..8])?;
125          let mut seeded_rng = rand_chacha::ChaChaRng::seed_from_u64(seed);
126  
127          // Iterate through the program functions and construct the callstacks and corresponding assignments.
128          for (function, (_, (verifying_key, _))) in
129              deployment.program().functions().values().zip_eq(deployment.verifying_keys())
130          {
131              // Initialize a burner private key.
132              let burner_private_key = PrivateKey::new(rng)?;
133              // Compute the burner address.
134              let burner_address = Address::try_from(&burner_private_key)?;
135              // Retrieve the input types.
136              let input_types = function.input_types();
137              // Retrieve the program checksum, if the program has a constructor.
138              let program_checksum = match self.program().contains_constructor() {
139                  true => Some(self.program_checksum_as_field()?),
140                  false => None,
141              };
142              // Sample the inputs.
143              let inputs = input_types
144                  .iter()
145                  .map(|input_type| match input_type {
146                      ValueType::ExternalRecord(locator) => {
147                          // Retrieve the external stack.
148                          let stack = self.get_external_stack(locator.program_id())?;
149                          // Sample the input.
150                          stack.sample_value(
151                              &burner_address,
152                              &ValueType::Record(*locator.resource()).into(),
153                              &mut seeded_rng,
154                          )
155                      }
156                      _ => self.sample_value(&burner_address, &input_type.into(), &mut seeded_rng),
157                  })
158                  .collect::<Result<Vec<_>>>()?;
159              lap!(timer, "Sample the inputs");
160              // Sample a dummy 'is_root'.
161              let is_root = true;
162  
163              // Compute the request, with a burner private key.
164              let request = Request::sign(
165                  &burner_private_key,
166                  *program_id,
167                  *function.name(),
168                  inputs.into_iter(),
169                  &input_types,
170                  root_tvk,
171                  is_root,
172                  program_checksum,
173                  rng,
174              )?;
175              lap!(timer, "Compute the request for {}", function.name());
176              // Initialize the assignments.
177              let assignments = Assignments::<N>::default();
178              // Initialize the constraint limit. Account for the constraint added after synthesis that makes the Varuna zerocheck hiding.
179              let Some(constraint_limit) = verifying_key.circuit_info.num_constraints.checked_sub(1) else {
180                  // Since a deployment must always pay non-zero fee, it must always have at least one constraint.
181                  bail!("The constraint limit of 0 for function '{}' is invalid", function.name());
182              };
183              // Retrieve the variable limit.
184              let variable_limit = verifying_key.num_variables();
185              // Initialize the call stack.
186              let call_stack = CallStack::CheckDeployment(
187                  vec![request],
188                  burner_private_key,
189                  assignments.clone(),
190                  Some(constraint_limit as u64),
191                  Some(variable_limit),
192              );
193              // Append the function name, callstack, and assignments.
194              call_stacks.push((function.name(), call_stack, assignments));
195          }
196  
197          // Verify the certificates.
198          let rngs = (0..call_stacks.len()).map(|_| StdRng::from_seed(seeded_rng.r#gen())).collect::<Vec<_>>();
199          cfg_into_iter!(call_stacks).zip_eq(deployment.verifying_keys()).zip_eq(rngs).try_for_each(
200              |(((function_name, call_stack, assignments), (_, (verifying_key, certificate))), mut rng)| {
201                  // Synthesize the circuit.
202                  if let Err(err) = self.execute_function::<A, _>(call_stack, caller, root_tvk, &mut rng) {
203                      bail!("Failed to synthesize the circuit for '{function_name}': {err}")
204                  }
205                  // Check the certificate.
206                  match assignments.read().last() {
207                      None => bail!("The assignment for function '{function_name}' is missing in '{program_id}'"),
208                      Some((assignment, _metrics)) => {
209                          // Ensure the certificate is valid.
210                          if !certificate.verify(&function_name.to_string(), assignment, verifying_key) {
211                              bail!("The certificate for function '{function_name}' is invalid in '{program_id}'")
212                          }
213                      }
214                  };
215                  Ok(())
216              },
217          )?;
218  
219          finish!(timer);
220  
221          Ok(())
222      }
223  }