diff --git a/circuit/environment/src/circuit.rs b/circuit/environment/src/circuit.rs index 56ff721b0c..7c3b0fc367 100644 --- a/circuit/environment/src/circuit.rs +++ b/circuit/environment/src/circuit.rs @@ -23,6 +23,7 @@ type Field = <console::MainnetV0 as console::Environment>::Field; thread_local! { pub(super) static CONSTRAINT_LIMIT: Cell<Option<u64>> = Cell::new(None); + pub(super) static VARIABLE_LIMIT: Cell<Option<u64>> = Cell::new(None); pub(super) static CIRCUIT: RefCell<R1CS<Field>> = RefCell::new(R1CS::new()); pub(super) static IN_WITNESS: Cell<bool> = Cell::new(false); pub(super) static ZERO: LinearCombination<Field> = LinearCombination::zero(); @@ -53,10 +54,20 @@ impl Environment for Circuit { IN_WITNESS.with(|in_witness| { // Ensure we are not in witness mode. if !in_witness.get() { - CIRCUIT.with(|circuit| match mode { - Mode::Constant => circuit.borrow_mut().new_constant(value), - Mode::Public => circuit.borrow_mut().new_public(value), - Mode::Private => circuit.borrow_mut().new_private(value), + CIRCUIT.with(|circuit| { + // Ensure that we do not surpass the variable limit for the circuit. + VARIABLE_LIMIT.with(|variable_limit| { + if let Some(limit) = variable_limit.get() { + if Self::num_variables() > limit { + Self::halt(format!("Surpassed the variable limit ({limit})")) + } + } + }); + match mode { + Mode::Constant => circuit.borrow_mut().new_constant(value), + Mode::Public => circuit.borrow_mut().new_public(value), + Mode::Private => circuit.borrow_mut().new_private(value), + } }) } else { Self::halt("Tried to initialize a new variable in witness mode") @@ -201,6 +212,11 @@ impl Environment for Circuit { CIRCUIT.with(|circuit| circuit.borrow().is_satisfied_in_scope()) } + /// Returns the total number of variables in the entire circuit. + fn num_variables() -> u64 { + CIRCUIT.with(|circuit| circuit.borrow().num_variables()) + } + /// Returns the number of constants in the entire circuit. fn num_constants() -> u64 { CIRCUIT.with(|circuit| circuit.borrow().num_constants()) @@ -268,6 +284,16 @@ impl Environment for Circuit { CONSTRAINT_LIMIT.with(|current_limit| current_limit.replace(limit)); } + /// Returns the variable limit for the circuit, if one exists. + fn get_variable_limit() -> Option<u64> { + VARIABLE_LIMIT.with(|current_limit| current_limit.get()) + } + + /// Sets the variable limit for the circuit. + fn set_variable_limit(limit: Option<u64>) { + VARIABLE_LIMIT.with(|current_limit| current_limit.replace(limit)); + } + /// Returns the R1CS circuit, resetting the circuit. fn inject_r1cs(r1cs: R1CS<Self::BaseField>) { CIRCUIT.with(|circuit| { @@ -275,6 +301,7 @@ impl Environment for Circuit { assert_eq!(0, circuit.borrow().num_constants()); assert_eq!(1, circuit.borrow().num_public()); assert_eq!(0, circuit.borrow().num_private()); + assert_eq!(1, circuit.borrow().num_variables()); assert_eq!(0, circuit.borrow().num_constraints()); // Inject the R1CS instance. let r1cs = circuit.replace(r1cs); @@ -282,6 +309,7 @@ impl Environment for Circuit { assert_eq!(0, r1cs.num_constants()); assert_eq!(1, r1cs.num_public()); assert_eq!(0, r1cs.num_private()); + assert_eq!(1, r1cs.num_variables()); assert_eq!(0, r1cs.num_constraints()); }) } @@ -293,12 +321,15 @@ impl Environment for Circuit { IN_WITNESS.with(|in_witness| in_witness.replace(false)); // Reset the constraint limit. Self::set_constraint_limit(None); + // Reset the variable limit. + Self::set_variable_limit(None); // Eject the R1CS instance. let r1cs = circuit.replace(R1CS::<<Self as Environment>::BaseField>::new()); // Ensure the circuit is now empty. assert_eq!(0, circuit.borrow().num_constants()); assert_eq!(1, circuit.borrow().num_public()); assert_eq!(0, circuit.borrow().num_private()); + assert_eq!(1, circuit.borrow().num_variables()); assert_eq!(0, circuit.borrow().num_constraints()); // Return the R1CS instance. r1cs @@ -312,11 +343,14 @@ impl Environment for Circuit { IN_WITNESS.with(|in_witness| in_witness.replace(false)); // Reset the constraint limit. Self::set_constraint_limit(None); + // Reset the variable limit. + Self::set_variable_limit(None); // Eject the R1CS instance. let r1cs = circuit.replace(R1CS::<<Self as Environment>::BaseField>::new()); assert_eq!(0, circuit.borrow().num_constants()); assert_eq!(1, circuit.borrow().num_public()); assert_eq!(0, circuit.borrow().num_private()); + assert_eq!(1, circuit.borrow().num_variables()); assert_eq!(0, circuit.borrow().num_constraints()); // Convert the R1CS instance to an assignment. Assignment::from(r1cs) @@ -330,11 +364,14 @@ impl Environment for Circuit { IN_WITNESS.with(|in_witness| in_witness.replace(false)); // Reset the constraint limit. Self::set_constraint_limit(None); + // Reset the variable limit. + Self::set_variable_limit(None); // Reset the circuit. *circuit.borrow_mut() = R1CS::<<Self as Environment>::BaseField>::new(); assert_eq!(0, circuit.borrow().num_constants()); assert_eq!(1, circuit.borrow().num_public()); assert_eq!(0, circuit.borrow().num_private()); + assert_eq!(1, circuit.borrow().num_variables()); assert_eq!(0, circuit.borrow().num_constraints()); }); } diff --git a/circuit/environment/src/environment.rs b/circuit/environment/src/environment.rs index 541da58919..b82984ebd0 100644 --- a/circuit/environment/src/environment.rs +++ b/circuit/environment/src/environment.rs @@ -110,6 +110,9 @@ pub trait Environment: 'static + Copy + Clone + fmt::Debug + fmt::Display + Eq + /// Returns `true` if all constraints in the current scope are satisfied. fn is_satisfied_in_scope() -> bool; + /// Returns the total number of variables in the entire environment. + fn num_variables() -> u64; + /// Returns the number of constants in the entire environment. fn num_constants() -> u64; @@ -167,6 +170,12 @@ pub trait Environment: 'static + Copy + Clone + fmt::Debug + fmt::Display + Eq + /// Sets the constraint limit for the circuit. fn set_constraint_limit(limit: Option<u64>); + /// Returns the variable limit for the circuit, if one exists. + fn get_variable_limit() -> Option<u64>; + + /// Sets the variable limit for the circuit. + fn set_variable_limit(limit: Option<u64>); + /// Returns the R1CS circuit, resetting the circuit. fn inject_r1cs(r1cs: R1CS<Self::BaseField>); diff --git a/circuit/environment/src/helpers/r1cs.rs b/circuit/environment/src/helpers/r1cs.rs index d08adbdfdb..e9f5ebaf67 100644 --- a/circuit/environment/src/helpers/r1cs.rs +++ b/circuit/environment/src/helpers/r1cs.rs @@ -29,6 +29,7 @@ pub struct R1CS<F: PrimeField> { private: Vec<Variable<F>>, constraints: Vec<Rc<Constraint<F>>>, counter: Counter<F>, + num_variables: u64, nonzeros: (u64, u64, u64), } @@ -41,6 +42,7 @@ impl<F: PrimeField> R1CS<F> { private: Default::default(), constraints: Default::default(), counter: Default::default(), + num_variables: 1, nonzeros: (0, 0, 0), } } @@ -60,6 +62,7 @@ impl<F: PrimeField> R1CS<F> { let variable = Variable::Constant(Rc::new(value)); self.constants.push(variable.clone()); self.counter.increment_constant(); + self.num_variables += 1; variable } @@ -68,6 +71,7 @@ impl<F: PrimeField> R1CS<F> { let variable = Variable::Public(Rc::new((self.public.len() as u64, value))); self.public.push(variable.clone()); self.counter.increment_public(); + self.num_variables += 1; variable } @@ -76,6 +80,7 @@ impl<F: PrimeField> R1CS<F> { let variable = Variable::Private(Rc::new((self.private.len() as u64, value))); self.private.push(variable.clone()); self.counter.increment_private(); + self.num_variables += 1; variable } @@ -134,6 +139,11 @@ impl<F: PrimeField> R1CS<F> { self.counter.scope() } + /// Returns the total number of variables in the constraint system. + pub fn num_variables(&self) -> u64 { + self.num_variables + } + /// Returns the number of constants in the constraint system. pub fn num_constants(&self) -> u64 { self.constants.len() as u64 diff --git a/circuit/environment/src/testnet_circuit.rs b/circuit/environment/src/testnet_circuit.rs index d220a80d7e..322d12ae33 100644 --- a/circuit/environment/src/testnet_circuit.rs +++ b/circuit/environment/src/testnet_circuit.rs @@ -23,6 +23,7 @@ type Field = <console::TestnetV0 as console::Environment>::Field; thread_local! { static CONSTRAINT_LIMIT: Cell<Option<u64>> = Cell::new(None); + static VARIABLE_LIMIT: Cell<Option<u64>> = Cell::new(None); pub(super) static TESTNET_CIRCUIT: RefCell<R1CS<Field>> = RefCell::new(R1CS::new()); static IN_WITNESS: Cell<bool> = Cell::new(false); static ZERO: LinearCombination<Field> = LinearCombination::zero(); @@ -53,6 +54,14 @@ impl Environment for TestnetCircuit { IN_WITNESS.with(|in_witness| { // Ensure we are not in witness mode. if !in_witness.get() { + // Ensure that we do not surpass the variable limit for the circuit. + VARIABLE_LIMIT.with(|variable_limit| { + if let Some(limit) = variable_limit.get() { + if Self::num_variables() > limit { + Self::halt(format!("Surpassed the variable limit ({limit})")) + } + } + }); TESTNET_CIRCUIT.with(|circuit| match mode { Mode::Constant => circuit.borrow_mut().new_constant(value), Mode::Public => circuit.borrow_mut().new_public(value), @@ -177,6 +186,11 @@ impl Environment for TestnetCircuit { TESTNET_CIRCUIT.with(|circuit| circuit.borrow().is_satisfied_in_scope()) } + /// Returns the total number of variables in the entire circuit. + fn num_variables() -> u64 { + TESTNET_CIRCUIT.with(|circuit| circuit.borrow().num_variables()) + } + /// Returns the number of constants in the entire circuit. fn num_constants() -> u64 { TESTNET_CIRCUIT.with(|circuit| circuit.borrow().num_constants()) @@ -244,6 +258,16 @@ impl Environment for TestnetCircuit { CONSTRAINT_LIMIT.with(|current_limit| current_limit.replace(limit)); } + /// Returns the variable limit for the circuit, if one exists. + fn get_variable_limit() -> Option<u64> { + VARIABLE_LIMIT.with(|current_limit| current_limit.get()) + } + + /// Sets the variable limit for the circuit. + fn set_variable_limit(limit: Option<u64>) { + VARIABLE_LIMIT.with(|current_limit| current_limit.replace(limit)); + } + /// Returns the R1CS circuit, resetting the circuit. fn inject_r1cs(r1cs: R1CS<Self::BaseField>) { TESTNET_CIRCUIT.with(|circuit| { @@ -251,6 +275,7 @@ impl Environment for TestnetCircuit { assert_eq!(0, circuit.borrow().num_constants()); assert_eq!(1, circuit.borrow().num_public()); assert_eq!(0, circuit.borrow().num_private()); + assert_eq!(1, circuit.borrow().num_variables()); assert_eq!(0, circuit.borrow().num_constraints()); // Inject the R1CS instance. let r1cs = circuit.replace(r1cs); @@ -258,6 +283,7 @@ impl Environment for TestnetCircuit { assert_eq!(0, r1cs.num_constants()); assert_eq!(1, r1cs.num_public()); assert_eq!(0, r1cs.num_private()); + assert_eq!(1, r1cs.num_variables()); assert_eq!(0, r1cs.num_constraints()); }) } @@ -269,12 +295,15 @@ impl Environment for TestnetCircuit { IN_WITNESS.with(|in_witness| in_witness.replace(false)); // Reset the constraint limit. Self::set_constraint_limit(None); + // Reset the variable limit. + Self::set_variable_limit(None); // Eject the R1CS instance. let r1cs = circuit.replace(R1CS::<<Self as Environment>::BaseField>::new()); // Ensure the circuit is now empty. assert_eq!(0, circuit.borrow().num_constants()); assert_eq!(1, circuit.borrow().num_public()); assert_eq!(0, circuit.borrow().num_private()); + assert_eq!(1, circuit.borrow().num_variables()); assert_eq!(0, circuit.borrow().num_constraints()); // Return the R1CS instance. r1cs @@ -288,11 +317,14 @@ impl Environment for TestnetCircuit { IN_WITNESS.with(|in_witness| in_witness.replace(false)); // Reset the constraint limit. Self::set_constraint_limit(None); + // Reset the variable limit. + Self::set_variable_limit(None); // Eject the R1CS instance. let r1cs = circuit.replace(R1CS::<<Self as Environment>::BaseField>::new()); assert_eq!(0, circuit.borrow().num_constants()); assert_eq!(1, circuit.borrow().num_public()); assert_eq!(0, circuit.borrow().num_private()); + assert_eq!(1, circuit.borrow().num_variables()); assert_eq!(0, circuit.borrow().num_constraints()); // Convert the R1CS instance to an assignment. Assignment::from(r1cs) @@ -306,11 +338,14 @@ impl Environment for TestnetCircuit { IN_WITNESS.with(|in_witness| in_witness.replace(false)); // Reset the constraint limit. Self::set_constraint_limit(None); + // Reset the variable limit. + Self::set_variable_limit(None); // Reset the circuit. *circuit.borrow_mut() = R1CS::<<Self as Environment>::BaseField>::new(); assert_eq!(0, circuit.borrow().num_constants()); assert_eq!(1, circuit.borrow().num_public()); assert_eq!(0, circuit.borrow().num_private()); + assert_eq!(1, circuit.borrow().num_variables()); assert_eq!(0, circuit.borrow().num_constraints()); }); } diff --git a/circuit/network/src/lib.rs b/circuit/network/src/lib.rs index dd6ac0a7fa..acabf0891a 100644 --- a/circuit/network/src/lib.rs +++ b/circuit/network/src/lib.rs @@ -29,6 +29,9 @@ pub trait Aleo: Environment { /// The maximum number of field elements in data (must not exceed u16::MAX). const MAX_DATA_SIZE_IN_FIELDS: u32 = <Self::Network as console::Network>::MAX_DATA_SIZE_IN_FIELDS; + /// Initializes all of the constants for the Aleo environment. + fn init_constants(); + /// Returns the encryption domain as a constant field element. fn encryption_domain() -> Field<Self>; diff --git a/circuit/network/src/testnet_v0.rs b/circuit/network/src/testnet_v0.rs index d782fdfb28..5917ab90ff 100644 --- a/circuit/network/src/testnet_v0.rs +++ b/circuit/network/src/testnet_v0.rs @@ -101,6 +101,29 @@ thread_local! { pub struct AleoTestnetV0; impl Aleo for AleoTestnetV0 { + /// Initializes all of the constants for the Aleo environment. + fn init_constants() { + GENERATOR_G.with(|_| ()); + ENCRYPTION_DOMAIN.with(|_| ()); + GRAPH_KEY_DOMAIN.with(|_| ()); + SERIAL_NUMBER_DOMAIN.with(|_| ()); + BHP_256.with(|_| ()); + BHP_512.with(|_| ()); + BHP_768.with(|_| ()); + BHP_1024.with(|_| ()); + KECCAK_256.with(|_| ()); + KECCAK_384.with(|_| ()); + KECCAK_512.with(|_| ()); + PEDERSEN_64.with(|_| ()); + PEDERSEN_128.with(|_| ()); + POSEIDON_2.with(|_| ()); + POSEIDON_4.with(|_| ()); + POSEIDON_8.with(|_| ()); + SHA3_256.with(|_| ()); + SHA3_384.with(|_| ()); + SHA3_512.with(|_| ()); + } + /// Returns the encryption domain as a constant field element. fn encryption_domain() -> Field<Self> { ENCRYPTION_DOMAIN.with(|domain| domain.clone()) @@ -411,6 +434,11 @@ impl Environment for AleoTestnetV0 { E::is_satisfied_in_scope() } + /// Returns the total number of variables in the entire circuit. + fn num_variables() -> u64 { + E::num_variables() + } + /// Returns the number of constants in the entire circuit. fn num_constants() -> u64 { E::num_constants() @@ -476,6 +504,16 @@ impl Environment for AleoTestnetV0 { E::set_constraint_limit(limit) } + /// Returns the variable limit for the circuit, if one exists. + fn get_variable_limit() -> Option<u64> { + E::get_variable_limit() + } + + /// Sets the constraint limit for the circuit. + fn set_variable_limit(limit: Option<u64>) { + E::set_variable_limit(limit) + } + /// Returns the R1CS circuit, resetting the circuit. fn inject_r1cs(r1cs: R1CS<Self::BaseField>) { E::inject_r1cs(r1cs) diff --git a/circuit/network/src/v0.rs b/circuit/network/src/v0.rs index 26bda8513c..0687634595 100644 --- a/circuit/network/src/v0.rs +++ b/circuit/network/src/v0.rs @@ -101,6 +101,29 @@ thread_local! { pub struct AleoV0; impl Aleo for AleoV0 { + /// Initializes all of the constants for the Aleo environment. + fn init_constants() { + GENERATOR_G.with(|_| ()); + ENCRYPTION_DOMAIN.with(|_| ()); + GRAPH_KEY_DOMAIN.with(|_| ()); + SERIAL_NUMBER_DOMAIN.with(|_| ()); + BHP_256.with(|_| ()); + BHP_512.with(|_| ()); + BHP_768.with(|_| ()); + BHP_1024.with(|_| ()); + KECCAK_256.with(|_| ()); + KECCAK_384.with(|_| ()); + KECCAK_512.with(|_| ()); + PEDERSEN_64.with(|_| ()); + PEDERSEN_128.with(|_| ()); + POSEIDON_2.with(|_| ()); + POSEIDON_4.with(|_| ()); + POSEIDON_8.with(|_| ()); + SHA3_256.with(|_| ()); + SHA3_384.with(|_| ()); + SHA3_512.with(|_| ()); + } + /// Returns the encryption domain as a constant field element. fn encryption_domain() -> Field<Self> { ENCRYPTION_DOMAIN.with(|domain| domain.clone()) @@ -411,6 +434,11 @@ impl Environment for AleoV0 { E::is_satisfied_in_scope() } + /// Returns the total number of variables in the entire circuit. + fn num_variables() -> u64 { + E::num_variables() + } + /// Returns the number of constants in the entire circuit. fn num_constants() -> u64 { E::num_constants() @@ -476,6 +504,16 @@ impl Environment for AleoV0 { E::set_constraint_limit(limit) } + /// Returns the variable limit for the circuit, if one exists. + fn get_variable_limit() -> Option<u64> { + E::get_variable_limit() + } + + /// Sets the constraint limit for the circuit. + fn set_variable_limit(limit: Option<u64>) { + E::set_variable_limit(limit) + } + /// Returns the R1CS circuit, resetting the circuit. fn inject_r1cs(r1cs: R1CS<Self::BaseField>) { E::inject_r1cs(r1cs) diff --git a/console/network/src/lib.rs b/console/network/src/lib.rs index 63af2ab15c..5f62a97f1b 100644 --- a/console/network/src/lib.rs +++ b/console/network/src/lib.rs @@ -116,9 +116,11 @@ pub trait Network: /// The cost in microcredits per byte for the deployment transaction. const DEPLOYMENT_FEE_MULTIPLIER: u64 = 1_000; // 1 millicredit per byte /// The cost in microcredits per constraint for the deployment transaction. - const SYNTHESIS_FEE_MULTIPLIER: u64 = 25; // 25 microcredits per constraint + const SYNTHESIS_FEE_MULTIPLIER: u64 = 12; // 12 microcredits per constraint /// The maximum number of constraints in a deployment. - const MAX_DEPLOYMENT_LIMIT: u64 = 1 << 20; // 1,048,576 constraints + const MAX_DEPLOYMENT_CONSTRAINTS: u64 = 1 << 20; // 1,048,576 constraints + /// The maximum number of variables in a deployment. + const MAX_DEPLOYMENT_VARIABLES: u64 = 1 << 20; // 1,048,576 variables /// The maximum number of microcredits that can be spent as a fee. const MAX_FEE: u64 = 1_000_000_000_000_000; /// The maximum number of microcredits that can be spent on a finalize block. diff --git a/ledger/block/src/transaction/deployment/bytes.rs b/ledger/block/src/transaction/deployment/bytes.rs index e8e97757a6..a4aed9266d 100644 --- a/ledger/block/src/transaction/deployment/bytes.rs +++ b/ledger/block/src/transaction/deployment/bytes.rs @@ -31,8 +31,8 @@ impl<N: Network> FromBytes for Deployment<N> { // Read the number of entries in the bundle. let num_entries = u16::read_le(&mut reader)?; - // Read the verifying keys. - let mut verifying_keys = Vec::with_capacity(num_entries as usize); + // Read the function specifications. + let mut function_specs = Vec::with_capacity(num_entries as usize); for _ in 0..num_entries { // Read the identifier. let identifier = Identifier::<N>::read_le(&mut reader)?; @@ -40,12 +40,14 @@ impl<N: Network> FromBytes for Deployment<N> { let verifying_key = VerifyingKey::<N>::read_le(&mut reader)?; // Read the certificate. let certificate = Certificate::<N>::read_le(&mut reader)?; + // Read the variable count. + let variable_count = u64::read_le(&mut reader)?; // Add the entry. - verifying_keys.push((identifier, (verifying_key, certificate))); + function_specs.push((identifier, (verifying_key, certificate, variable_count))); } // Return the deployment. - Self::new(edition, program, verifying_keys).map_err(|err| error(format!("{err}"))) + Self::new(edition, program, function_specs).map_err(|err| error(format!("{err}"))) } } @@ -59,15 +61,17 @@ impl<N: Network> ToBytes for Deployment<N> { // Write the program. self.program.write_le(&mut writer)?; // Write the number of entries in the bundle. - (u16::try_from(self.verifying_keys.len()).map_err(|e| error(e.to_string()))?).write_le(&mut writer)?; + (u16::try_from(self.function_specs.len()).map_err(|e| error(e.to_string()))?).write_le(&mut writer)?; // Write each entry. - for (function_name, (verifying_key, certificate)) in &self.verifying_keys { + for (function_name, (verifying_key, certificate, variable_count)) in &self.function_specs { // Write the function name. function_name.write_le(&mut writer)?; // Write the verifying key. verifying_key.write_le(&mut writer)?; // Write the certificate. certificate.write_le(&mut writer)?; + // Write the variable count. + variable_count.write_le(&mut writer)?; } Ok(()) } diff --git a/ledger/block/src/transaction/deployment/mod.rs b/ledger/block/src/transaction/deployment/mod.rs index bf898b41ba..105f5b12ee 100644 --- a/ledger/block/src/transaction/deployment/mod.rs +++ b/ledger/block/src/transaction/deployment/mod.rs @@ -27,25 +27,27 @@ use console::{ use synthesizer_program::Program; use synthesizer_snark::{Certificate, VerifyingKey}; +/// The verification key, certificate, and number of variables for a function. +type FunctionSpec<N> = (VerifyingKey<N>, Certificate<N>, u64); + #[derive(Clone, PartialEq, Eq)] pub struct Deployment<N: Network> { /// The edition. edition: u16, /// The program. program: Program<N>, - /// The mapping of function names to their verifying key and certificate. - verifying_keys: Vec<(Identifier<N>, (VerifyingKey<N>, Certificate<N>))>, + /// The mapping of function names to their specification. + function_specs: Vec<(Identifier<N>, FunctionSpec<N>)>, } - impl<N: Network> Deployment<N> { /// Initializes a new deployment. pub fn new( edition: u16, program: Program<N>, - verifying_keys: Vec<(Identifier<N>, (VerifyingKey<N>, Certificate<N>))>, + function_specs: Vec<(Identifier<N>, FunctionSpec<N>)>, ) -> Result<Self> { // Construct the deployment. - let deployment = Self { edition, program, verifying_keys }; + let deployment = Self { edition, program, function_specs }; // Ensure the deployment is ordered. deployment.check_is_ordered()?; // Return the deployment. @@ -70,17 +72,17 @@ impl<N: Network> Deployment<N> { ); // Ensure the deployment contains verifying keys. ensure!( - !self.verifying_keys.is_empty(), + !self.function_specs.is_empty(), "No verifying keys present in the deployment for program '{program_id}'" ); - // Ensure the number of functions matches the number of verifying keys. - if self.program.functions().len() != self.verifying_keys.len() { + // Ensure the number of functions matches the number of function specifications. + if self.program.functions().len() != self.function_specs.len() { bail!("Deployment has an incorrect number of verifying keys, according to the program."); } - // Ensure the function and verifying keys correspond. - for ((function_name, function), (name, _)) in self.program.functions().iter().zip_eq(&self.verifying_keys) { + // Ensure the function and specs correspond. + for ((function_name, function), (name, _)) in self.program.functions().iter().zip_eq(&self.function_specs) { // Ensure the function name is correct. if function_name != function.name() { bail!("The function key is '{function_name}', but the function name is '{}'", function.name()) @@ -92,7 +94,7 @@ impl<N: Network> Deployment<N> { } ensure!( - !has_duplicates(self.verifying_keys.iter().map(|(name, ..)| name)), + !has_duplicates(self.function_specs.iter().map(|(name, ..)| name)), "A duplicate function name was found" ); @@ -119,9 +121,9 @@ impl<N: Network> Deployment<N> { self.program.id() } - /// Returns the verifying keys. - pub const fn verifying_keys(&self) -> &Vec<(Identifier<N>, (VerifyingKey<N>, Certificate<N>))> { - &self.verifying_keys + /// Returns the function specifications. + pub const fn verifying_keys(&self) -> &Vec<(Identifier<N>, FunctionSpec<N>)> { + &self.function_specs } /// Returns the sum of the constraint counts for all functions in this deployment. @@ -129,7 +131,7 @@ impl<N: Network> Deployment<N> { // Initialize the accumulator. let mut num_combined_constraints = 0u64; // Iterate over the functions. - for (_, (vk, _)) in &self.verifying_keys { + for (_, (vk, _, _)) in &self.function_specs { // Add the number of constraints. // Note: This method must be *checked* because the claimed constraint count // is from the user, not the synthesizer. @@ -141,6 +143,23 @@ impl<N: Network> Deployment<N> { Ok(num_combined_constraints) } + /// Returns the sum of the variable counts for all functions in this deployment. + pub fn num_combined_variables(&self) -> Result<u64> { + // Initialize the accumulator. + let mut num_combined_variables = 0u64; + // Iterate over the functions. + for (_, (_, _, variable_count)) in &self.function_specs { + // Add the number of variables. + // Note: This method must be *checked* because the claimed variable count + // is from the user, not the synthesizer. + num_combined_variables = num_combined_variables + .checked_add(*variable_count) + .ok_or_else(|| anyhow!("Overflow when counting variables for '{}'", self.program_id()))?; + } + // Return the number of combined constraints. + Ok(num_combined_variables) + } + /// Returns the deployment ID. pub fn to_deployment_id(&self) -> Result<Field<N>> { Ok(*Transaction::deployment_tree(self, None)?.root()) diff --git a/ledger/block/src/transaction/deployment/serialize.rs b/ledger/block/src/transaction/deployment/serialize.rs index 88af802fc9..ee9ac51cd5 100644 --- a/ledger/block/src/transaction/deployment/serialize.rs +++ b/ledger/block/src/transaction/deployment/serialize.rs @@ -22,7 +22,7 @@ impl<N: Network> Serialize for Deployment<N> { let mut deployment = serializer.serialize_struct("Deployment", 3)?; deployment.serialize_field("edition", &self.edition)?; deployment.serialize_field("program", &self.program)?; - deployment.serialize_field("verifying_keys", &self.verifying_keys)?; + deployment.serialize_field("function_specs", &self.function_specs)?; deployment.end() } false => ToBytesSerializer::serialize_with_size_encoding(self, serializer), @@ -45,7 +45,7 @@ impl<'de, N: Network> Deserialize<'de> for Deployment<N> { // Retrieve the program. DeserializeExt::take_from_value::<D>(&mut deployment, "program")?, // Retrieve the verifying keys. - DeserializeExt::take_from_value::<D>(&mut deployment, "verifying_keys")?, + DeserializeExt::take_from_value::<D>(&mut deployment, "function_specs")?, ) .map_err(de::Error::custom)?; diff --git a/ledger/store/src/helpers/memory/transaction.rs b/ledger/store/src/helpers/memory/transaction.rs index 66b7b14867..eb80a51af4 100644 --- a/ledger/store/src/helpers/memory/transaction.rs +++ b/ledger/store/src/helpers/memory/transaction.rs @@ -103,6 +103,8 @@ pub struct DeploymentMemory<N: Network> { verifying_key_map: MemoryMap<(ProgramID<N>, Identifier<N>, u16), VerifyingKey<N>>, /// The certificate map. certificate_map: MemoryMap<(ProgramID<N>, Identifier<N>, u16), Certificate<N>>, + /// The variable count map. + variable_count_map: MemoryMap<(ProgramID<N>, Identifier<N>, u16), u64>, /// The fee store. fee_store: FeeStore<N, FeeMemory<N>>, } @@ -116,6 +118,7 @@ impl<N: Network> DeploymentStorage<N> for DeploymentMemory<N> { type ProgramMap = MemoryMap<(ProgramID<N>, u16), Program<N>>; type VerifyingKeyMap = MemoryMap<(ProgramID<N>, Identifier<N>, u16), VerifyingKey<N>>; type CertificateMap = MemoryMap<(ProgramID<N>, Identifier<N>, u16), Certificate<N>>; + type VariableCountMap = MemoryMap<(ProgramID<N>, Identifier<N>, u16), u64>; type FeeStorage = FeeMemory<N>; /// Initializes the deployment storage. @@ -128,6 +131,7 @@ impl<N: Network> DeploymentStorage<N> for DeploymentMemory<N> { program_map: MemoryMap::default(), verifying_key_map: MemoryMap::default(), certificate_map: MemoryMap::default(), + variable_count_map: MemoryMap::default(), fee_store, }) } @@ -167,6 +171,11 @@ impl<N: Network> DeploymentStorage<N> for DeploymentMemory<N> { &self.certificate_map } + /// Returns the variable count map. + fn variable_count_map(&self) -> &Self::VariableCountMap { + &self.variable_count_map + } + /// Returns the fee store. fn fee_store(&self) -> &FeeStore<N, Self::FeeStorage> { &self.fee_store diff --git a/ledger/store/src/helpers/rocksdb/internal/id.rs b/ledger/store/src/helpers/rocksdb/internal/id.rs index 6093fdad74..4ada48b0c1 100644 --- a/ledger/store/src/helpers/rocksdb/internal/id.rs +++ b/ledger/store/src/helpers/rocksdb/internal/id.rs @@ -110,6 +110,7 @@ pub enum DeploymentMap { Program = DataID::DeploymentProgramMap as u16, VerifyingKey = DataID::DeploymentVerifyingKeyMap as u16, Certificate = DataID::DeploymentCertificateMap as u16, + VariableCount = DataID::DeploymentVariableCountMap as u16, } /// The RocksDB map prefix for execution-related entries. @@ -252,6 +253,7 @@ enum DataID { DeploymentProgramMap, DeploymentVerifyingKeyMap, DeploymentCertificateMap, + DeploymentVariableCountMap, // Execution ExecutionIDMap, ExecutionReverseIDMap, diff --git a/ledger/store/src/helpers/rocksdb/transaction.rs b/ledger/store/src/helpers/rocksdb/transaction.rs index 2cac1c9c2c..ff1610d77e 100644 --- a/ledger/store/src/helpers/rocksdb/transaction.rs +++ b/ledger/store/src/helpers/rocksdb/transaction.rs @@ -113,6 +113,8 @@ pub struct DeploymentDB<N: Network> { verifying_key_map: DataMap<(ProgramID<N>, Identifier<N>, u16), VerifyingKey<N>>, /// The certificate map. certificate_map: DataMap<(ProgramID<N>, Identifier<N>, u16), Certificate<N>>, + /// The variable count map. + variable_count_map: DataMap<(ProgramID<N>, Identifier<N>, u16), u64>, /// The fee store. fee_store: FeeStore<N, FeeDB<N>>, } @@ -126,6 +128,7 @@ impl<N: Network> DeploymentStorage<N> for DeploymentDB<N> { type ProgramMap = DataMap<(ProgramID<N>, u16), Program<N>>; type VerifyingKeyMap = DataMap<(ProgramID<N>, Identifier<N>, u16), VerifyingKey<N>>; type CertificateMap = DataMap<(ProgramID<N>, Identifier<N>, u16), Certificate<N>>; + type VariableCountMap = DataMap<(ProgramID<N>, Identifier<N>, u16), u64>; type FeeStorage = FeeDB<N>; /// Initializes the deployment storage. @@ -140,6 +143,7 @@ impl<N: Network> DeploymentStorage<N> for DeploymentDB<N> { program_map: rocksdb::RocksDB::open_map(N::ID, storage_mode.clone(), MapID::Deployment(DeploymentMap::Program))?, verifying_key_map: rocksdb::RocksDB::open_map(N::ID, storage_mode.clone(), MapID::Deployment(DeploymentMap::VerifyingKey))?, certificate_map: rocksdb::RocksDB::open_map(N::ID, storage_mode.clone(), MapID::Deployment(DeploymentMap::Certificate))?, + variable_count_map: rocksdb::RocksDB::open_map(N::ID, storage_mode.clone(), MapID::Deployment(DeploymentMap::VariableCount))?, fee_store, }) } @@ -179,6 +183,11 @@ impl<N: Network> DeploymentStorage<N> for DeploymentDB<N> { &self.certificate_map } + /// Returns the variable count map. + fn variable_count_map(&self) -> &Self::VariableCountMap { + &self.variable_count_map + } + /// Returns the fee store. fn fee_store(&self) -> &FeeStore<N, Self::FeeStorage> { &self.fee_store diff --git a/ledger/store/src/transaction/deployment.rs b/ledger/store/src/transaction/deployment.rs index bdc7d95b46..1b41181c41 100644 --- a/ledger/store/src/transaction/deployment.rs +++ b/ledger/store/src/transaction/deployment.rs @@ -49,6 +49,8 @@ pub trait DeploymentStorage<N: Network>: Clone + Send + Sync { type VerifyingKeyMap: for<'a> Map<'a, (ProgramID<N>, Identifier<N>, u16), VerifyingKey<N>>; /// The mapping of `(program ID, function name, edition)` to `certificate`. type CertificateMap: for<'a> Map<'a, (ProgramID<N>, Identifier<N>, u16), Certificate<N>>; + /// The mapping of `(program ID, function name, edition)` to `variable count`. + type VariableCountMap: for<'a> Map<'a, (ProgramID<N>, Identifier<N>, u16), u64>; /// The fee storage. type FeeStorage: FeeStorage<N>; @@ -69,6 +71,8 @@ pub trait DeploymentStorage<N: Network>: Clone + Send + Sync { fn verifying_key_map(&self) -> &Self::VerifyingKeyMap; /// Returns the certificate map. fn certificate_map(&self) -> &Self::CertificateMap; + /// Returns the variable count map. + fn variable_count_map(&self) -> &Self::VariableCountMap; /// Returns the fee storage. fn fee_store(&self) -> &FeeStore<N, Self::FeeStorage>; @@ -196,11 +200,13 @@ pub trait DeploymentStorage<N: Network>: Clone + Send + Sync { self.program_map().insert((program_id, edition), program.clone())?; // Store the verifying keys and certificates. - for (function_name, (verifying_key, certificate)) in deployment.verifying_keys() { + for (function_name, (verifying_key, certificate, variable_count)) in deployment.verifying_keys() { // Store the verifying key. self.verifying_key_map().insert((program_id, *function_name, edition), verifying_key.clone())?; // Store the certificate. self.certificate_map().insert((program_id, *function_name, edition), certificate.clone())?; + // Store the variable count. + self.variable_count_map().insert((program_id, *function_name, edition), *variable_count)?; } // Store the fee transition. @@ -400,27 +406,37 @@ pub trait DeploymentStorage<N: Network>: Clone + Send + Sync { None => bail!("Failed to get the deployed program '{program_id}' (edition {edition})"), }; - // Initialize a vector for the verifying keys and certificates. - let mut verifying_keys = Vec::with_capacity(program.functions().len()); + // Initialize a vector for the function_specs. + let mut function_specs = Vec::with_capacity(program.functions().len()); // Retrieve the verifying keys and certificates. for function_name in program.functions().keys() { // Retrieve the verifying key. let verifying_key = match self.verifying_key_map().get_confirmed(&(program_id, *function_name, edition))? { Some(verifying_key) => cow_to_cloned!(verifying_key), - None => bail!("Failed to get the verifying key for '{program_id}/{function_name}' (edition {edition})"), + None => { + bail!("Failed to get the verifying key for '{program_id}/{function_name}' (edition {edition})") + } }; // Retrieve the certificate. let certificate = match self.certificate_map().get_confirmed(&(program_id, *function_name, edition))? { Some(certificate) => cow_to_cloned!(certificate), None => bail!("Failed to get the certificate for '{program_id}/{function_name}' (edition {edition})"), }; - // Add the verifying key and certificate to the deployment. - verifying_keys.push((*function_name, (verifying_key, certificate))); + // Retrieve the variable count. + let variable_count = + match self.variable_count_map().get_confirmed(&(program_id, *function_name, edition))? { + Some(variable_count) => cow_to_cloned!(variable_count), + None => { + bail!("Failed to get the variable count for '{program_id}/{function_name}' (edition {edition})") + } + }; + // Add the verifying key, certificate and variable_count to the deployment. + function_specs.push((*function_name, (verifying_key, certificate, variable_count))); } // Return the deployment. - Ok(Some(Deployment::new(edition, program, verifying_keys)?)) + Ok(Some(Deployment::new(edition, program, function_specs)?)) } /// Returns the fee for the given `transaction ID`. diff --git a/synthesizer/process/src/cost.rs b/synthesizer/process/src/cost.rs index f97a3bf45c..8f6132e6ca 100644 --- a/synthesizer/process/src/cost.rs +++ b/synthesizer/process/src/cost.rs @@ -31,6 +31,8 @@ pub fn deployment_cost<N: Network>(deployment: &Deployment<N>) -> Result<(u64, ( let num_characters = u32::try_from(program_id.name().to_string().len())?; // Compute the number of combined constraints in the program. let num_combined_constraints = deployment.num_combined_constraints()?; + // Compute the number of combined variables in the program. + let num_combined_variables = deployment.num_combined_variables()?; // Compute the storage cost in microcredits. let storage_cost = size_in_bytes @@ -38,7 +40,7 @@ pub fn deployment_cost<N: Network>(deployment: &Deployment<N>) -> Result<(u64, ( .ok_or(anyhow!("The storage cost computation overflowed for a deployment"))?; // Compute the synthesis cost in microcredits. - let synthesis_cost = num_combined_constraints * N::SYNTHESIS_FEE_MULTIPLIER; + let synthesis_cost = (num_combined_variables + num_combined_constraints) * N::SYNTHESIS_FEE_MULTIPLIER; // Compute the namespace cost in credits: 10^(10 - num_characters). let namespace_cost = 10u64 diff --git a/synthesizer/process/src/deploy.rs b/synthesizer/process/src/deploy.rs index b876a37f0e..5bd6a23487 100644 --- a/synthesizer/process/src/deploy.rs +++ b/synthesizer/process/src/deploy.rs @@ -48,7 +48,7 @@ impl<N: Network> Process<N> { lap!(timer, "Compute the stack"); // Insert the verifying keys. - for (function_name, (verifying_key, _)) in deployment.verifying_keys() { + for (function_name, (verifying_key, _, _)) in deployment.verifying_keys() { stack.insert_verifying_key(function_name, verifying_key.clone())?; } lap!(timer, "Insert the verifying keys"); diff --git a/synthesizer/process/src/finalize.rs b/synthesizer/process/src/finalize.rs index 5b420ac064..04d482929b 100644 --- a/synthesizer/process/src/finalize.rs +++ b/synthesizer/process/src/finalize.rs @@ -38,7 +38,7 @@ impl<N: Network> Process<N> { lap!(timer, "Compute the stack"); // Insert the verifying keys. - for (function_name, (verifying_key, _)) in deployment.verifying_keys() { + for (function_name, (verifying_key, _, _)) in deployment.verifying_keys() { stack.insert_verifying_key(function_name, verifying_key.clone())?; } lap!(timer, "Insert the verifying keys"); diff --git a/synthesizer/process/src/stack/deploy.rs b/synthesizer/process/src/stack/deploy.rs index be662d134f..fe36958976 100644 --- a/synthesizer/process/src/stack/deploy.rs +++ b/synthesizer/process/src/stack/deploy.rs @@ -37,6 +37,8 @@ impl<N: Network> Stack<N> { let proving_key = self.get_proving_key(function_name)?; // Retrieve the verifying key. let verifying_key = self.get_verifying_key(function_name)?; + // Retrieve the function variables. + let variable_count = self.get_variable_count(function_name)?; lap!(timer, "Retrieve the keys for {function_name}"); // Certify the circuit. @@ -44,7 +46,7 @@ impl<N: Network> Stack<N> { lap!(timer, "Certify the circuit"); // Add the verifying key and certificate to the bundle. - verifying_keys.push((*function_name, (verifying_key, certificate))); + verifying_keys.push((*function_name, (verifying_key, certificate, variable_count))); } finish!(timer); @@ -74,7 +76,10 @@ impl<N: Network> Stack<N> { let program_id = self.program.id(); // Check that the number of combined constraints does not exceed the deployment limit. - ensure!(deployment.num_combined_constraints()? <= N::MAX_DEPLOYMENT_LIMIT); + ensure!(deployment.num_combined_constraints()? <= N::MAX_DEPLOYMENT_CONSTRAINTS); + + // Check that the number of combined variables does not exceed the deployment limit. + ensure!(deployment.num_combined_variables()? <= N::MAX_DEPLOYMENT_VARIABLES); // Construct the call stacks and assignments used to verify the certificates. let mut call_stacks = Vec::with_capacity(deployment.verifying_keys().len()); @@ -92,7 +97,7 @@ impl<N: Network> Stack<N> { ); // Iterate through the program functions and construct the callstacks and corresponding assignments. - for (function, (_, (verifying_key, _))) in + for (function, (_, (verifying_key, _, variable_limit))) in deployment.program().functions().values().zip_eq(deployment.verifying_keys()) { // Initialize a burner private key. @@ -137,12 +142,14 @@ impl<N: Network> Stack<N> { // Since a deployment must always pay non-zero fee, it must always have at least one constraint. bail!("The constraint limit of 0 for function '{}' is invalid", function.name()); }; + // Initialize the call stack. let call_stack = CallStack::CheckDeployment( vec![request], burner_private_key, assignments.clone(), Some(constraint_limit as u64), + Some(*variable_limit), ); // Append the function name, callstack, and assignments. call_stacks.push((function.name(), call_stack, assignments)); @@ -151,7 +158,7 @@ impl<N: Network> Stack<N> { // Verify the certificates. let rngs = (0..call_stacks.len()).map(|_| StdRng::from_seed(rng.gen())).collect::<Vec<_>>(); cfg_into_iter!(call_stacks).zip_eq(deployment.verifying_keys()).zip_eq(rngs).try_for_each( - |(((function_name, call_stack, assignments), (_, (verifying_key, certificate))), mut rng)| { + |(((function_name, call_stack, assignments), (_, (verifying_key, certificate, _))), mut rng)| { // Synthesize the circuit. if let Err(err) = self.execute_function::<A, _>(call_stack, caller, root_tvk, &mut rng) { bail!("Failed to synthesize the circuit for '{function_name}': {err}") diff --git a/synthesizer/process/src/stack/execute.rs b/synthesizer/process/src/stack/execute.rs index 0039eb4f03..1e2cf80e86 100644 --- a/synthesizer/process/src/stack/execute.rs +++ b/synthesizer/process/src/stack/execute.rs @@ -144,13 +144,16 @@ impl<N: Network> StackExecute<N> for Stack<N> { ) -> Result<Response<N>> { let timer = timer!("Stack::execute_function"); + // Ensure constant circuit gadgets are initialized. + A::init_constants(); // Ensure the circuit environment is clean. A::reset(); // If in 'CheckDeployment' mode, set the constraint limit. // We do not have to reset it after function calls because `CheckDeployment` mode does not execute those. - if let CallStack::CheckDeployment(_, _, _, constraint_limit) = &call_stack { + if let CallStack::CheckDeployment(_, _, _, constraint_limit, variable_limit) = &call_stack { A::set_constraint_limit(*constraint_limit); + A::set_variable_limit(*variable_limit); } // Retrieve the next request. @@ -422,6 +425,9 @@ impl<N: Network> StackExecute<N> for Stack<N> { ); } + // Determine the number of variables allocated in the circuit. + let num_variables = A::num_variables(); + // Eject the circuit assignment and reset the circuit. let assignment = A::eject_assignment_and_reset(); @@ -432,7 +438,7 @@ impl<N: Network> StackExecute<N> for Stack<N> { // If the proving key does not exist, then synthesize it. if !self.contains_proving_key(function.name()) { // Add the circuit key to the mapping. - self.synthesize_from_assignment(function.name(), &assignment)?; + self.synthesize_from_assignment(function.name(), &assignment, num_variables)?; lap!(timer, "Synthesize the {} circuit key", function.name()); } } @@ -445,7 +451,7 @@ impl<N: Network> StackExecute<N> for Stack<N> { lap!(timer, "Save the transition"); } // If the circuit is in `CheckDeployment` mode, then save the assignment. - else if let CallStack::CheckDeployment(_, _, ref assignments, _) = registers.call_stack() { + else if let CallStack::CheckDeployment(_, _, ref assignments, _, _) = registers.call_stack() { // Construct the call metrics. let metrics = CallMetrics { program_id: *self.program_id(), diff --git a/synthesizer/process/src/stack/helpers/initialize.rs b/synthesizer/process/src/stack/helpers/initialize.rs index ea1c35f370..0340eb66ce 100644 --- a/synthesizer/process/src/stack/helpers/initialize.rs +++ b/synthesizer/process/src/stack/helpers/initialize.rs @@ -27,6 +27,7 @@ impl<N: Network> Stack<N> { universal_srs: process.universal_srs().clone(), proving_keys: Default::default(), verifying_keys: Default::default(), + variable_counts: Default::default(), number_of_calls: Default::default(), finalize_costs: Default::default(), program_depth: 0, diff --git a/synthesizer/process/src/stack/helpers/synthesize.rs b/synthesizer/process/src/stack/helpers/synthesize.rs index 72d7d7bc24..e5532a7374 100644 --- a/synthesizer/process/src/stack/helpers/synthesize.rs +++ b/synthesizer/process/src/stack/helpers/synthesize.rs @@ -80,6 +80,8 @@ impl<N: Network> Stack<N> { ensure!(self.contains_proving_key(function_name), "Function '{function_name}' is missing a proving key."); // Ensure the verifying key exists. ensure!(self.contains_verifying_key(function_name), "Function '{function_name}' is missing a verifying key."); + // Ensure the variable count exists. + ensure!(self.contains_variable_count(function_name), "Function '{function_name}' is missing variable count."); Ok(()) } @@ -89,6 +91,7 @@ impl<N: Network> Stack<N> { &self, function_name: &Identifier<N>, assignment: &circuit::Assignment<N::Field>, + variable_count: u64, ) -> Result<()> { // If the proving and verifying key already exist, skip the synthesis for this function. if self.contains_proving_key(function_name) && self.contains_verifying_key(function_name) { @@ -100,6 +103,8 @@ impl<N: Network> Stack<N> { // Insert the proving key. self.insert_proving_key(function_name, proving_key)?; // Insert the verifying key. - self.insert_verifying_key(function_name, verifying_key) + self.insert_verifying_key(function_name, verifying_key)?; + // Insert the variable count. + self.insert_variable_count(function_name, variable_count) } } diff --git a/synthesizer/process/src/stack/mod.rs b/synthesizer/process/src/stack/mod.rs index aa0f7af67b..9b391080fa 100644 --- a/synthesizer/process/src/stack/mod.rs +++ b/synthesizer/process/src/stack/mod.rs @@ -81,7 +81,7 @@ pub type Assignments<N> = Arc<RwLock<Vec<(circuit::Assignment<<N as Environment> pub enum CallStack<N: Network> { Authorize(Vec<Request<N>>, PrivateKey<N>, Authorization<N>), Synthesize(Vec<Request<N>>, PrivateKey<N>, Authorization<N>), - CheckDeployment(Vec<Request<N>>, PrivateKey<N>, Assignments<N>, Option<u64>), + CheckDeployment(Vec<Request<N>>, PrivateKey<N>, Assignments<N>, Option<u64>, Option<u64>), Evaluate(Authorization<N>), Execute(Authorization<N>, Arc<RwLock<Trace<N>>>), PackageRun(Vec<Request<N>>, PrivateKey<N>, Assignments<N>), @@ -109,12 +109,13 @@ impl<N: Network> CallStack<N> { CallStack::Synthesize(requests, private_key, authorization) => { CallStack::Synthesize(requests.clone(), *private_key, authorization.replicate()) } - CallStack::CheckDeployment(requests, private_key, assignments, constraint_limit) => { + CallStack::CheckDeployment(requests, private_key, assignments, constraint_limit, variable_limit) => { CallStack::CheckDeployment( requests.clone(), *private_key, Arc::new(RwLock::new(assignments.read().clone())), *constraint_limit, + *variable_limit, ) } CallStack::Evaluate(authorization) => CallStack::Evaluate(authorization.replicate()), @@ -185,6 +186,8 @@ pub struct Stack<N: Network> { proving_keys: Arc<RwLock<IndexMap<Identifier<N>, ProvingKey<N>>>>, /// The mapping of function name to verifying key. verifying_keys: Arc<RwLock<IndexMap<Identifier<N>, VerifyingKey<N>>>>, + /// The mapping of function name to number of variables. + variable_counts: Arc<RwLock<IndexMap<Identifier<N>, u64>>>, /// The mapping of function names to the number of calls. number_of_calls: IndexMap<Identifier<N>, usize>, /// The mapping of function names to finalize cost. @@ -376,6 +379,12 @@ impl<N: Network> Stack<N> { self.verifying_keys.read().contains_key(function_name) } + /// Returns `true` if the function variables for the given function name exists. + #[inline] + pub fn contains_variable_count(&self, function_name: &Identifier<N>) -> bool { + self.variable_counts.read().contains_key(function_name) + } + /// Returns the proving key for the given function name. #[inline] pub fn get_proving_key(&self, function_name: &Identifier<N>) -> Result<ProvingKey<N>> { @@ -398,6 +407,16 @@ impl<N: Network> Stack<N> { } } + /// Returns the variable count for the given function name. + #[inline] + pub fn get_variable_count(&self, function_name: &Identifier<N>) -> Result<u64> { + // Return the number of function variables, if it exists. + match self.variable_counts.read().get(function_name) { + Some(variable_count) => Ok(*variable_count), + None => bail!("Variable count not found for: {}/{function_name}", self.program.id()), + } + } + /// Inserts the given proving key for the given function name. #[inline] pub fn insert_proving_key(&self, function_name: &Identifier<N>, proving_key: ProvingKey<N>) -> Result<()> { @@ -426,6 +445,20 @@ impl<N: Network> Stack<N> { Ok(()) } + /// Inserts the given variable count for the given function name. + #[inline] + pub fn insert_variable_count(&self, function_name: &Identifier<N>, variable_count: u64) -> Result<()> { + // Ensure the function name exists in the program. + ensure!( + self.program.contains_function(function_name), + "Function '{function_name}' does not exist in program '{}'.", + self.program.id() + ); + // Insert the number of function variables. + self.variable_counts.write().insert(*function_name, variable_count); + Ok(()) + } + /// Removes the proving key for the given function name. #[inline] pub fn remove_proving_key(&self, function_name: &Identifier<N>) { diff --git a/synthesizer/process/src/tests/test_credits.rs b/synthesizer/process/src/tests/test_credits.rs index 0986c204f9..003d6068b8 100644 --- a/synthesizer/process/src/tests/test_credits.rs +++ b/synthesizer/process/src/tests/test_credits.rs @@ -2249,7 +2249,7 @@ mod sanity_checks { // Initialize the assignments. let assignments = Assignments::<N>::default(); // Initialize the call stack. - let call_stack = CallStack::CheckDeployment(vec![request], *private_key, assignments.clone(), None); + let call_stack = CallStack::CheckDeployment(vec![request], *private_key, assignments.clone(), None, None); // Synthesize the circuit. let _response = stack.execute_function::<A, _>(call_stack, None, None, rng).unwrap(); // Retrieve the assignment. diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index 5c78a84fb6..68793f96b8 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -1031,7 +1031,7 @@ function a: // Note: `deployment_transaction_ids` is sorted lexicographically by transaction ID, so the order may change if we update internal methods. assert_eq!( deployment_transaction_ids, - vec![deployment_1.id(), deployment_4.id(), deployment_3.id(), deployment_2.id()], + vec![deployment_1.id(), deployment_3.id(), deployment_2.id(), deployment_4.id()], "Update me if serialization has changed" ); } @@ -1320,6 +1320,50 @@ function do: assert!(vm.check_transaction(&deployment, None, rng).is_err()); } + #[test] + fn test_deployment_num_constant_overload() { + let rng = &mut TestRng::default(); + + // Initialize a private key. + let private_key = sample_genesis_private_key(rng); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Deploy the base program. + let program = Program::from_str( + r" +program synthesis_num_constants.aleo; + +function do: + cast 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 into r0 as [u32; 32u32]; + cast r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 into r1 as [[u32; 32u32]; 32u32]; + cast r1 r1 r1 r1 r1 into r2 as [[[u32; 32u32]; 32u32]; 5u32]; + hash.bhp1024 r2 into r3 as u32; + output r3 as u32.private; + +function do2: + cast 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 0u32 into r0 as [u32; 32u32]; + cast r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 into r1 as [[u32; 32u32]; 32u32]; + cast r1 r1 r1 r1 r1 into r2 as [[[u32; 32u32]; 32u32]; 5u32]; + hash.bhp1024 r2 into r3 as u32; + output r3 as u32.private;", + ) + .unwrap(); + + // Create the deployment transaction. + let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + + // Verify the deployment transaction. It should fail because there are too many constants. + let check_tx_res = vm.check_transaction(&deployment, None, rng); + assert!(check_tx_res.is_err()); + } + #[test] fn test_deployment_synthesis_overreport() { let rng = &mut TestRng::default(); @@ -1357,11 +1401,11 @@ function do: // Increase the number of constraints in the verifying keys. let mut vks_with_overreport = Vec::with_capacity(deployment.verifying_keys().len()); - for (id, (vk, cert)) in deployment.verifying_keys() { + for (id, (vk, cert, vars)) in deployment.verifying_keys() { let mut vk = vk.deref().clone(); vk.circuit_info.num_constraints += 1; let vk = VerifyingKey::new(Arc::new(vk)); - vks_with_overreport.push((*id, (vk, cert.clone()))); + vks_with_overreport.push((*id, (vk, cert.clone(), *vars))); } // Each additional constraint costs 25 microcredits, so we need to increase the fee by 25 microcredits. @@ -1421,11 +1465,11 @@ function do: // Decrease the number of constraints in the verifying keys. let mut vks_with_underreport = Vec::with_capacity(deployment.verifying_keys().len()); - for (id, (vk, cert)) in deployment.verifying_keys() { + for (id, (vk, cert, vars)) in deployment.verifying_keys() { let mut vk = vk.deref().clone(); vk.circuit_info.num_constraints -= 2; let vk = VerifyingKey::new(Arc::new(vk)); - vks_with_underreport.push((*id, (vk, cert.clone()))); + vks_with_underreport.push((*id, (vk, cert.clone(), *vars))); } // Create a new deployment transaction with the underreported verifying keys. @@ -1459,6 +1503,79 @@ function do: vm.add_next_block(&block).unwrap(); } + #[test] + fn test_deployment_variable_underreport() { + let rng = &mut TestRng::default(); + + // Initialize a private key. + let private_key = sample_genesis_private_key(rng); + let address = Address::try_from(&private_key).unwrap(); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Deploy the base program. + let program = Program::from_str( + r" +program synthesis_underreport.aleo; + +function do: + input r0 as u32.private; + add r0 r0 into r1; + output r1 as u32.public;", + ) + .unwrap(); + + // Create the deployment transaction. + let transaction = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + + // Destructure the deployment transaction. + let Transaction::Deploy(txid, program_owner, deployment, fee) = transaction else { + panic!("Expected a deployment transaction"); + }; + + // Decrease the number of reported variables in the verifying keys. + let mut vks_with_underreport = Vec::with_capacity(deployment.verifying_keys().len()); + for (id, (vk, cert, vars)) in deployment.verifying_keys() { + vks_with_underreport.push((*id, (vk.clone(), cert.clone(), vars - 2))); + } + + // Create a new deployment transaction with the underreported verifying keys. + let adjusted_deployment = + Deployment::new(deployment.edition(), deployment.program().clone(), vks_with_underreport).unwrap(); + let adjusted_transaction = Transaction::Deploy(txid, program_owner, Box::new(adjusted_deployment), fee); + + // Verify the deployment transaction. It should error when synthesizing the first variable over the vk limit. + let result = vm.check_transaction(&adjusted_transaction, None, rng); + assert!(result.is_err()); + + // Create a standard transaction + // Prepare the inputs. + let inputs = [ + Value::<CurrentNetwork>::from_str(&address.to_string()).unwrap(), + Value::<CurrentNetwork>::from_str("1u64").unwrap(), + ] + .into_iter(); + + // Execute. + let transaction = + vm.execute(&private_key, ("credits.aleo", "transfer_public"), inputs, None, 0, None, rng).unwrap(); + + // Check that the deployment transaction will be aborted if injected into a block. + let block = sample_next_block(&vm, &private_key, &[transaction, adjusted_transaction.clone()], rng).unwrap(); + + // Check that the block aborts the deployment transaction. + assert_eq!(block.aborted_transaction_ids(), &vec![adjusted_transaction.id()]); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + } + #[test] #[ignore] fn test_deployment_memory_overload() { @@ -1847,7 +1964,7 @@ finalize transfer_public: Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, _ => panic!("Expected a valid balance"), }; - assert_eq!(balance, 182_499_997_483_583, "Update me if the initial balance changes."); + assert_eq!(balance, 182_499_997_403_803, "Update me if the initial balance changes."); // Check the balance of the `credits_wrapper` program. let balance = match vm @@ -1899,7 +2016,7 @@ finalize transfer_public: Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, _ => panic!("Expected a valid balance"), }; - assert_eq!(balance, 182_499_997_431_058, "Update me if the initial balance changes."); + assert_eq!(balance, 182_499_997_351_278, "Update me if the initial balance changes."); // Check the balance of the `credits_wrapper` program. let balance = match vm @@ -2038,7 +2155,7 @@ finalize transfer_public_as_signer: Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, _ => panic!("Expected a valid balance"), }; - assert_eq!(balance, 182_499_997_412_068, "Update me if the initial balance changes."); + assert_eq!(balance, 182_499_997_321_968, "Update me if the initial balance changes."); // Check the `credits_wrapper` program does not have any balance. let balance = vm @@ -2193,7 +2310,7 @@ finalize transfer_public_to_private: _ => panic!("Expected a valid balance"), }; - assert_eq!(balance, 182_499_996_924_681, "Update me if the initial balance changes."); + assert_eq!(balance, 182_499_996_794_967, "Update me if the initial balance changes."); // Check that the `credits_wrapper` program has a balance of 0. let balance = match vm diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/test_rand.out b/synthesizer/tests/expectations/vm/execute_and_finalize/test_rand.out index 51fb5b5e60..d71649dc35 100644 --- a/synthesizer/tests/expectations/vm/execute_and_finalize/test_rand.out +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/test_rand.out @@ -26,7 +26,7 @@ outputs: test_rand.aleo/rand_chacha_check: outputs: - '{"type":"future","id":"818878742790741579153893179075772445872751227433677932822653185952935999557field","value":"{\n program_id: test_rand.aleo,\n function_name: rand_chacha_check,\n arguments: [\n 1field,\n true\n ]\n}"}' - speculate: the execution was accepted + speculate: the execution was rejected add_next_block: succeeded. additional: - child_outputs: