From 88364b06b23b38d7c5a81ef153f29be4902908ef Mon Sep 17 00:00:00 2001 From: Victor Sint Nicolaas Date: Fri, 29 Dec 2023 20:00:31 +0100 Subject: [PATCH 01/35] Limit the number of allowed constraints for deployments --- circuit/environment/src/circuit.rs | 19 +++++++++++++++++ circuit/environment/src/environment.rs | 3 +++ circuit/network/src/v0.rs | 5 +++++ console/network/src/lib.rs | 4 ++++ .../block/src/transaction/deployment/mod.rs | 5 +++++ synthesizer/process/src/stack/call/mod.rs | 5 +++++ synthesizer/process/src/stack/deploy.rs | 21 +++++++++++++++---- synthesizer/process/src/stack/execute.rs | 7 ++++++- synthesizer/process/src/stack/mod.rs | 15 +++++++------ synthesizer/process/src/tests/test_credits.rs | 5 ++++- synthesizer/src/vm/deploy.rs | 2 +- synthesizer/src/vm/helpers/cost.rs | 13 +++++++++--- 12 files changed, 88 insertions(+), 16 deletions(-) diff --git a/circuit/environment/src/circuit.rs b/circuit/environment/src/circuit.rs index be60fe946b..5f7fc42821 100644 --- a/circuit/environment/src/circuit.rs +++ b/circuit/environment/src/circuit.rs @@ -24,6 +24,7 @@ type Field = ::Field; thread_local! { pub(super) static CIRCUIT: RefCell> = RefCell::new(R1CS::new()); pub(super) static IN_WITNESS: Cell = Cell::new(false); + pub(super) static MAX_NUM_CONSTRAINTS: Cell = Cell::new(u64::MAX); pub(super) static ZERO: LinearCombination = LinearCombination::zero(); pub(super) static ONE: LinearCombination = LinearCombination::one(); } @@ -146,6 +147,13 @@ impl Environment for Circuit { // Ensure we are not in witness mode. if !in_witness.get() { CIRCUIT.with(|circuit| { + // Ensure we do not surpass maximum allowed number of constraints + MAX_NUM_CONSTRAINTS.with(|max_constraints| { + if circuit.borrow().num_constraints() > max_constraints.get() { + Self::halt("Surpassing maximum allowed number of constraints") + } + }); + let (a, b, c) = constraint(); let (a, b, c) = (a.into(), b.into(), c.into()); @@ -275,6 +283,8 @@ impl Environment for Circuit { CIRCUIT.with(|circuit| { // Reset the witness mode. IN_WITNESS.with(|in_witness| in_witness.replace(false)); + // Reset the max num constraints. + MAX_NUM_CONSTRAINTS.with(|max_num_constraints| max_num_constraints.replace(u64::MAX)); // Eject the R1CS instance. let r1cs = circuit.replace(R1CS::<::BaseField>::new()); // Ensure the circuit is now empty. @@ -294,6 +304,8 @@ impl Environment for Circuit { CIRCUIT.with(|circuit| { // Reset the witness mode. IN_WITNESS.with(|in_witness| in_witness.replace(false)); + // Reset the num constraints. + MAX_NUM_CONSTRAINTS.with(|max_num_constraints| max_num_constraints.replace(u64::MAX)); // Eject the R1CS instance. let r1cs = circuit.replace(R1CS::<::BaseField>::new()); assert_eq!(0, circuit.borrow().num_constants()); @@ -305,11 +317,18 @@ impl Environment for Circuit { }) } + /// Sets a maximum amount of allowed constraints + fn set_constraint_maximum(new_max_num_constraints: u64) { + MAX_NUM_CONSTRAINTS.with(|max_num_constraints| max_num_constraints.replace(new_max_num_constraints)); + } + /// Clears the circuit and initializes an empty environment. fn reset() { CIRCUIT.with(|circuit| { // Reset the witness mode. IN_WITNESS.with(|in_witness| in_witness.replace(false)); + // Reset the max num constraints. + MAX_NUM_CONSTRAINTS.with(|max_num_constraints| max_num_constraints.replace(u64::MAX)); *circuit.borrow_mut() = R1CS::<::BaseField>::new(); assert_eq!(0, circuit.borrow().num_constants()); assert_eq!(1, circuit.borrow().num_public()); diff --git a/circuit/environment/src/environment.rs b/circuit/environment/src/environment.rs index 34ef4370ea..789cfd61a2 100644 --- a/circuit/environment/src/environment.rs +++ b/circuit/environment/src/environment.rs @@ -169,6 +169,9 @@ pub trait Environment: 'static + Copy + Clone + fmt::Debug + fmt::Display + Eq + /// Returns the R1CS assignment of the circuit, resetting the circuit. fn eject_assignment_and_reset() -> Assignment<::Field>; + /// Sets a maximum amount of allowed constraints + fn set_constraint_maximum(new_max_num_constraints: u64); + /// Clears and initializes an empty environment. fn reset(); } diff --git a/circuit/network/src/v0.rs b/circuit/network/src/v0.rs index 9dc7a9d8da..7f76af350e 100644 --- a/circuit/network/src/v0.rs +++ b/circuit/network/src/v0.rs @@ -481,6 +481,11 @@ impl Environment for AleoV0 { E::eject_assignment_and_reset() } + /// Sets a maximum amount of allowed constraints + fn set_constraint_maximum(new_max_num_constraints: u64) { + E::set_constraint_maximum(new_max_num_constraints) + } + /// Clears the circuit and initializes an empty environment. fn reset() { E::reset() diff --git a/console/network/src/lib.rs b/console/network/src/lib.rs index 717946cba2..20f8e94201 100644 --- a/console/network/src/lib.rs +++ b/console/network/src/lib.rs @@ -98,6 +98,10 @@ pub trait Network: const STARTING_SUPPLY: u64 = 1_500_000_000_000_000; // 1.5B credits /// 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 SYNTH_FEE_MULTIPLIER: u64 = 25; // 25 microcredit per constraint to synthesize + /// The maximum number of constraints in a deployment + const MAX_DEPLOYMENT_CONSTRAINTS: u64 = 300_000; /// The maximum number of microcredits that can be spent as a fee. const MAX_FEE: u64 = 1_000_000_000_000_000; diff --git a/ledger/block/src/transaction/deployment/mod.rs b/ledger/block/src/transaction/deployment/mod.rs index 01d4cac081..ddb4790c2d 100644 --- a/ledger/block/src/transaction/deployment/mod.rs +++ b/ledger/block/src/transaction/deployment/mod.rs @@ -124,6 +124,11 @@ impl Deployment { &self.verifying_keys } + /// Returns the total number of constraints. + pub fn num_constraints(&self) -> u64 { + self.verifying_keys.iter().map(|(_, (vk, _))| vk.circuit_info.num_constraints).sum::() as u64 + } + /// Returns the deployment ID. pub fn to_deployment_id(&self) -> Result> { Ok(*Transaction::deployment_tree(self, None)?.root()) diff --git a/synthesizer/process/src/stack/call/mod.rs b/synthesizer/process/src/stack/call/mod.rs index 5d6578ab73..e22c0c33ca 100644 --- a/synthesizer/process/src/stack/call/mod.rs +++ b/synthesizer/process/src/stack/call/mod.rs @@ -298,6 +298,11 @@ impl CallTrait for Call { // Inject the existing circuit. A::inject_r1cs(r1cs); + // If the circuit is in CheckDeployment mode, set a constraint maximum. + if let CallStack::CheckDeployment(_, _, _, num_constraints) = ®isters.call_stack() { + A::set_constraint_maximum(*num_constraints); + } + use circuit::Inject; // Inject the network ID as `Mode::Constant`. diff --git a/synthesizer/process/src/stack/deploy.rs b/synthesizer/process/src/stack/deploy.rs index 39b46f2f6f..08e158adac 100644 --- a/synthesizer/process/src/stack/deploy.rs +++ b/synthesizer/process/src/stack/deploy.rs @@ -73,11 +73,17 @@ impl Stack { let program_id = self.program.id(); + // Check that the deployment does not require too many constraints + let total_num_constraints = deployment.num_constraints(); + ensure!(total_num_constraints <= N::MAX_DEPLOYMENT_CONSTRAINTS); + // Construct the call stacks and assignments used to verify the certificates. let mut call_stacks = Vec::with_capacity(deployment.verifying_keys().len()); // Iterate through the program functions and construct the callstacks and corresponding assignments. - for function in deployment.program().functions().values() { + for (function, (_, (verifying_key, _))) in + deployment.program().functions().values().zip_eq(deployment.verifying_keys()) + { // Initialize a burner private key. let burner_private_key = PrivateKey::new(rng)?; // Compute the burner address. @@ -109,20 +115,27 @@ impl Stack { rng, )?; lap!(timer, "Compute the request for {}", function.name()); + // Get the expected number of constraints + let expected_num_constraints = verifying_key.circuit_info.num_constraints as u64; // Initialize the assignments. let assignments = Assignments::::default(); // Initialize the call stack. - let call_stack = CallStack::CheckDeployment(vec![request], burner_private_key, assignments.clone()); + let call_stack = CallStack::CheckDeployment( + vec![request], + burner_private_key, + assignments.clone(), + expected_num_constraints, + ); // Append the function name, callstack, and assignments. call_stacks.push((function.name(), call_stack, assignments)); } // Verify the certificates. let rngs = (0..call_stacks.len()).map(|_| StdRng::from_seed(rng.gen())).collect::>(); - cfg_iter!(call_stacks).zip_eq(deployment.verifying_keys()).zip_eq(rngs).try_for_each( + 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)| { // Synthesize the circuit. - if let Err(err) = self.execute_function::(call_stack.clone(), None, &mut rng) { + if let Err(err) = self.execute_function::(call_stack, None, &mut rng) { bail!("Failed to synthesize the circuit for '{function_name}': {err}") } // Check the certificate. diff --git a/synthesizer/process/src/stack/execute.rs b/synthesizer/process/src/stack/execute.rs index 20758e5e84..9215f02c72 100644 --- a/synthesizer/process/src/stack/execute.rs +++ b/synthesizer/process/src/stack/execute.rs @@ -142,6 +142,11 @@ impl StackExecute for Stack { // Ensure the circuit environment is clean. A::reset(); + // If the circuit is in CheckDeployment mode, set a constraint maximum. + if let CallStack::CheckDeployment(_, _, _, num_constraints) = &call_stack { + A::set_constraint_maximum(*num_constraints); + } + // Retrieve the next request. let console_request = call_stack.pop()?; @@ -416,7 +421,7 @@ impl StackExecute for Stack { 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/mod.rs b/synthesizer/process/src/stack/mod.rs index 2b95b46fb7..739e1ac9c8 100644 --- a/synthesizer/process/src/stack/mod.rs +++ b/synthesizer/process/src/stack/mod.rs @@ -79,7 +79,7 @@ pub type Assignments = Arc pub enum CallStack { Authorize(Vec>, PrivateKey, Authorization), Synthesize(Vec>, PrivateKey, Authorization), - CheckDeployment(Vec>, PrivateKey, Assignments), + CheckDeployment(Vec>, PrivateKey, Assignments, u64), Evaluate(Authorization), Execute(Authorization, Arc>>), PackageRun(Vec>, PrivateKey, Assignments), @@ -107,11 +107,14 @@ impl CallStack { CallStack::Synthesize(requests, private_key, authorization) => { CallStack::Synthesize(requests.clone(), *private_key, authorization.replicate()) } - CallStack::CheckDeployment(requests, private_key, assignments) => CallStack::CheckDeployment( - requests.clone(), - *private_key, - Arc::new(RwLock::new(assignments.read().clone())), - ), + CallStack::CheckDeployment(requests, private_key, assignments, num_constraints) => { + CallStack::CheckDeployment( + requests.clone(), + *private_key, + Arc::new(RwLock::new(assignments.read().clone())), + *num_constraints, + ) + } CallStack::Evaluate(authorization) => CallStack::Evaluate(authorization.replicate()), CallStack::Execute(authorization, trace) => { CallStack::Execute(authorization.replicate(), Arc::new(RwLock::new(trace.read().clone()))) diff --git a/synthesizer/process/src/tests/test_credits.rs b/synthesizer/process/src/tests/test_credits.rs index aefae59a3c..9b2b2d3732 100644 --- a/synthesizer/process/src/tests/test_credits.rs +++ b/synthesizer/process/src/tests/test_credits.rs @@ -1524,8 +1524,11 @@ mod sanity_checks { let request = Request::sign(private_key, program_id, function_name, inputs.iter(), &input_types, rng).unwrap(); // Initialize the assignments. let assignments = Assignments::::default(); + // Set max num constraints + let max_num_constraints = u64::MAX; // Initialize the call stack. - let call_stack = CallStack::CheckDeployment(vec![request], *private_key, assignments.clone()); + let call_stack = + CallStack::CheckDeployment(vec![request], *private_key, assignments.clone(), max_num_constraints); // Synthesize the circuit. let _response = stack.execute_function::(call_stack, None, rng).unwrap(); // Retrieve the assignment. diff --git a/synthesizer/src/vm/deploy.rs b/synthesizer/src/vm/deploy.rs index e080ba54d9..f9e260793c 100644 --- a/synthesizer/src/vm/deploy.rs +++ b/synthesizer/src/vm/deploy.rs @@ -40,7 +40,7 @@ impl> VM { let owner = ProgramOwner::new(private_key, deployment_id, rng)?; // Compute the minimum deployment cost. - let (minimum_deployment_cost, (_, _)) = deployment_cost(&deployment)?; + let (minimum_deployment_cost, _) = deployment_cost(&deployment)?; // Authorize the fee. let fee_authorization = match fee_record { Some(record) => self.authorize_fee_private( diff --git a/synthesizer/src/vm/helpers/cost.rs b/synthesizer/src/vm/helpers/cost.rs index 6f349baa84..7e90b98ad7 100644 --- a/synthesizer/src/vm/helpers/cost.rs +++ b/synthesizer/src/vm/helpers/cost.rs @@ -23,14 +23,16 @@ use synthesizer_program::{Command, Finalize, Instruction}; use std::collections::HashMap; -/// Returns the *minimum* cost in microcredits to publish the given deployment (total cost, (storage cost, namespace cost)). -pub fn deployment_cost(deployment: &Deployment) -> Result<(u64, (u64, u64))> { +/// Returns the *minimum* cost in microcredits to publish the given deployment (total cost, (storage cost, namespace cost, synthesis cost)). +pub fn deployment_cost(deployment: &Deployment) -> Result<(u64, (u64, u64, u64))> { // Determine the number of bytes in the deployment. let size_in_bytes = deployment.size_in_bytes()?; // Retrieve the program ID. let program_id = deployment.program_id(); // Determine the number of characters in the program ID. let num_characters = u32::try_from(program_id.name().to_string().len())?; + // Determine the number of constraints in the program + let num_constraints = deployment.num_constraints(); // Compute the storage cost in microcredits. let storage_cost = size_in_bytes @@ -43,12 +45,17 @@ pub fn deployment_cost(deployment: &Deployment) -> Result<(u64, ( .ok_or(anyhow!("The namespace cost computation overflowed for a deployment"))? .saturating_mul(1_000_000); // 1 microcredit = 1e-6 credits. + // Compute the synthesis cost in credits + let synthesis_cost = num_constraints * N::SYNTH_FEE_MULTIPLIER; + // Compute the total cost in microcredits. let total_cost = storage_cost .checked_add(namespace_cost) + .ok_or(anyhow!("The total cost computation overflowed for a deployment"))? + .checked_add(synthesis_cost) .ok_or(anyhow!("The total cost computation overflowed for a deployment"))?; - Ok((total_cost, (storage_cost, namespace_cost))) + Ok((total_cost, (storage_cost, namespace_cost, synthesis_cost))) } /// Returns the *minimum* cost in microcredits to publish the given execution (total cost, (storage cost, namespace cost)). From 5e0e2f6f4da9720f18219f1ac24d42d6f4d90dc1 Mon Sep 17 00:00:00 2001 From: Victor Sint Nicolaas Date: Fri, 29 Dec 2023 21:27:42 +0100 Subject: [PATCH 02/35] Increase allowed number of deployment constraints for existing test suite --- console/network/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/console/network/src/lib.rs b/console/network/src/lib.rs index 20f8e94201..69807dc6db 100644 --- a/console/network/src/lib.rs +++ b/console/network/src/lib.rs @@ -101,7 +101,7 @@ pub trait Network: /// The cost in microcredits per constraint for the deployment transaction. const SYNTH_FEE_MULTIPLIER: u64 = 25; // 25 microcredit per constraint to synthesize /// The maximum number of constraints in a deployment - const MAX_DEPLOYMENT_CONSTRAINTS: u64 = 300_000; + const MAX_DEPLOYMENT_CONSTRAINTS: u64 = 1_000_000; /// The maximum number of microcredits that can be spent as a fee. const MAX_FEE: u64 = 1_000_000_000_000_000; From f42970198d6dc5c0cabc49b00539541cf05a1fde Mon Sep 17 00:00:00 2001 From: Victor Sint Nicolaas Date: Tue, 2 Jan 2024 13:46:30 +0100 Subject: [PATCH 03/35] Correct test expectations. The RNG depends on FinalizeState --- .../tests/expectations/vm/execute_and_finalize/test_rand.out | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 4fefc62cfd..d753dcf2b4 100644 --- a/synthesizer/tests/expectations/vm/execute_and_finalize/test_rand.out +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/test_rand.out @@ -19,14 +19,14 @@ outputs: test_rand.aleo/rand_chacha_check: outputs: - '{"type":"future","id":"3721325135151760660773959530505944451747681933722462808964783147996869797702field","value":"{\n program_id: test_rand.aleo,\n function_name: rand_chacha_check,\n arguments: [\n 0field,\n false\n ]\n}"}' - speculate: the execution was accepted + speculate: the execution was rejected add_next_block: succeeded. - verified: true execute: test_rand.aleo/rand_chacha_check: outputs: - '{"type":"future","id":"887371549615679800380522845098080464570119184210350810479392117984911457950field","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: From 696d33282ed4b409ebaa0049cdf9dec2bf4fe1b7 Mon Sep 17 00:00:00 2001 From: Victor Sint Nicolaas Date: Tue, 2 Jan 2024 14:10:14 +0100 Subject: [PATCH 04/35] Add test for too many constraints --- synthesizer/src/vm/mod.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index a9e29d1594..d4ba2bab2b 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -1077,6 +1077,40 @@ function check: assert!(vm.contains_program(&ProgramID::from_str("parent_program.aleo").unwrap())); } + #[test] + fn test_deployment_synthesis_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 program_layer_0.aleo; + +function do: + input r0 as [[u128; 32u32]; 2u32].private; + hash.sha3_256 r0 into r1 as field; + output r1 as field.public;", + ) + .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 constraints. + assert!(vm.check_transaction(&deployment, None, rng).is_err()); + } + #[test] #[ignore] fn test_deployment_memory_overload() { From d872a4231e428823440b2d52f0a13d7bf692bb8e Mon Sep 17 00:00:00 2001 From: Victor Sint Nicolaas Date: Wed, 10 Jan 2024 17:10:14 +0100 Subject: [PATCH 05/35] Actually limit at max_constraints --- circuit/environment/src/circuit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circuit/environment/src/circuit.rs b/circuit/environment/src/circuit.rs index 5f7fc42821..8c94971752 100644 --- a/circuit/environment/src/circuit.rs +++ b/circuit/environment/src/circuit.rs @@ -149,7 +149,7 @@ impl Environment for Circuit { CIRCUIT.with(|circuit| { // Ensure we do not surpass maximum allowed number of constraints MAX_NUM_CONSTRAINTS.with(|max_constraints| { - if circuit.borrow().num_constraints() > max_constraints.get() { + if circuit.borrow().num_constraints() >= max_constraints.get() { Self::halt("Surpassing maximum allowed number of constraints") } }); From 0be9950400419f3de502c242d4771d44a3bc9054 Mon Sep 17 00:00:00 2001 From: Victor Sint Nicolaas Date: Wed, 10 Jan 2024 17:12:31 +0100 Subject: [PATCH 06/35] Use Self::set_constraint_maximum --- circuit/environment/src/circuit.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/circuit/environment/src/circuit.rs b/circuit/environment/src/circuit.rs index 8c94971752..5055788ed5 100644 --- a/circuit/environment/src/circuit.rs +++ b/circuit/environment/src/circuit.rs @@ -284,7 +284,7 @@ impl Environment for Circuit { // Reset the witness mode. IN_WITNESS.with(|in_witness| in_witness.replace(false)); // Reset the max num constraints. - MAX_NUM_CONSTRAINTS.with(|max_num_constraints| max_num_constraints.replace(u64::MAX)); + Self::set_constraint_maximum(u64::MAX); // Eject the R1CS instance. let r1cs = circuit.replace(R1CS::<::BaseField>::new()); // Ensure the circuit is now empty. @@ -305,7 +305,7 @@ impl Environment for Circuit { // Reset the witness mode. IN_WITNESS.with(|in_witness| in_witness.replace(false)); // Reset the num constraints. - MAX_NUM_CONSTRAINTS.with(|max_num_constraints| max_num_constraints.replace(u64::MAX)); + Self::set_constraint_maximum(u64::MAX); // Eject the R1CS instance. let r1cs = circuit.replace(R1CS::<::BaseField>::new()); assert_eq!(0, circuit.borrow().num_constants()); @@ -328,7 +328,7 @@ impl Environment for Circuit { // Reset the witness mode. IN_WITNESS.with(|in_witness| in_witness.replace(false)); // Reset the max num constraints. - MAX_NUM_CONSTRAINTS.with(|max_num_constraints| max_num_constraints.replace(u64::MAX)); + Self::set_constraint_maximum(u64::MAX); *circuit.borrow_mut() = R1CS::<::BaseField>::new(); assert_eq!(0, circuit.borrow().num_constants()); assert_eq!(1, circuit.borrow().num_public()); From 06ab667560e62ae06facacb110953f7d9a9345b2 Mon Sep 17 00:00:00 2001 From: Victor Sint Nicolaas Date: Wed, 10 Jan 2024 17:13:02 +0100 Subject: [PATCH 07/35] Comment nit --- circuit/environment/src/circuit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circuit/environment/src/circuit.rs b/circuit/environment/src/circuit.rs index 5055788ed5..505e33f16a 100644 --- a/circuit/environment/src/circuit.rs +++ b/circuit/environment/src/circuit.rs @@ -317,7 +317,7 @@ impl Environment for Circuit { }) } - /// Sets a maximum amount of allowed constraints + /// Sets a maximum number of allowed constraints. fn set_constraint_maximum(new_max_num_constraints: u64) { MAX_NUM_CONSTRAINTS.with(|max_num_constraints| max_num_constraints.replace(new_max_num_constraints)); } From 6b6fc407ace68b53be636f07ebea5a97350538a7 Mon Sep 17 00:00:00 2001 From: Victor Sint Nicolaas Date: Wed, 10 Jan 2024 17:14:08 +0100 Subject: [PATCH 08/35] Convert type early --- ledger/block/src/transaction/deployment/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ledger/block/src/transaction/deployment/mod.rs b/ledger/block/src/transaction/deployment/mod.rs index ddb4790c2d..c253c66ec2 100644 --- a/ledger/block/src/transaction/deployment/mod.rs +++ b/ledger/block/src/transaction/deployment/mod.rs @@ -126,7 +126,7 @@ impl Deployment { /// Returns the total number of constraints. pub fn num_constraints(&self) -> u64 { - self.verifying_keys.iter().map(|(_, (vk, _))| vk.circuit_info.num_constraints).sum::() as u64 + self.verifying_keys.iter().map(|(_, (vk, _))| vk.circuit_info.num_constraints as u64).sum::() } /// Returns the deployment ID. From 726a614d761c2d0eb7353f370623e83b627b2551 Mon Sep 17 00:00:00 2001 From: Victor Sint Nicolaas Date: Wed, 10 Jan 2024 17:15:53 +0100 Subject: [PATCH 09/35] Check that the number of functions matches the number of verifying keys --- synthesizer/process/src/stack/deploy.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/synthesizer/process/src/stack/deploy.rs b/synthesizer/process/src/stack/deploy.rs index 08e158adac..5217c3b709 100644 --- a/synthesizer/process/src/stack/deploy.rs +++ b/synthesizer/process/src/stack/deploy.rs @@ -80,6 +80,11 @@ impl Stack { // Construct the call stacks and assignments used to verify the certificates. let mut call_stacks = Vec::with_capacity(deployment.verifying_keys().len()); + // Check that the number of functions matches the number of verifying keys. + ensure!( + deployment.program().functions().len() == deployment.verifying_keys().len(), + "The number of functions in the program does not match the number of verifying keys" + ); // Iterate through the program functions and construct the callstacks and corresponding assignments. for (function, (_, (verifying_key, _))) in deployment.program().functions().values().zip_eq(deployment.verifying_keys()) From 457334845b922b29d8509f7aa468b99d99100134 Mon Sep 17 00:00:00 2001 From: Victor Sint Nicolaas Date: Wed, 10 Jan 2024 17:18:20 +0100 Subject: [PATCH 10/35] Clean up deployment_cost function --- synthesizer/src/vm/deploy.rs | 2 +- synthesizer/src/vm/helpers/cost.rs | 9 ++++----- synthesizer/src/vm/verify.rs | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/synthesizer/src/vm/deploy.rs b/synthesizer/src/vm/deploy.rs index f9e260793c..f548607d52 100644 --- a/synthesizer/src/vm/deploy.rs +++ b/synthesizer/src/vm/deploy.rs @@ -40,7 +40,7 @@ impl> VM { let owner = ProgramOwner::new(private_key, deployment_id, rng)?; // Compute the minimum deployment cost. - let (minimum_deployment_cost, _) = deployment_cost(&deployment)?; + let minimum_deployment_cost = deployment_cost(&deployment)?; // Authorize the fee. let fee_authorization = match fee_record { Some(record) => self.authorize_fee_private( diff --git a/synthesizer/src/vm/helpers/cost.rs b/synthesizer/src/vm/helpers/cost.rs index 7e90b98ad7..4b69e5cd83 100644 --- a/synthesizer/src/vm/helpers/cost.rs +++ b/synthesizer/src/vm/helpers/cost.rs @@ -23,8 +23,8 @@ use synthesizer_program::{Command, Finalize, Instruction}; use std::collections::HashMap; -/// Returns the *minimum* cost in microcredits to publish the given deployment (total cost, (storage cost, namespace cost, synthesis cost)). -pub fn deployment_cost(deployment: &Deployment) -> Result<(u64, (u64, u64, u64))> { +/// Returns the *minimum* cost in microcredits to publish the given deployment total cost. +pub fn deployment_cost(deployment: &Deployment) -> Result { // Determine the number of bytes in the deployment. let size_in_bytes = deployment.size_in_bytes()?; // Retrieve the program ID. @@ -51,11 +51,10 @@ pub fn deployment_cost(deployment: &Deployment) -> Result<(u64, ( // Compute the total cost in microcredits. let total_cost = storage_cost .checked_add(namespace_cost) - .ok_or(anyhow!("The total cost computation overflowed for a deployment"))? - .checked_add(synthesis_cost) + .and_then(|x| x.checked_add(synthesis_cost)) .ok_or(anyhow!("The total cost computation overflowed for a deployment"))?; - Ok((total_cost, (storage_cost, namespace_cost, synthesis_cost))) + Ok(total_cost) } /// Returns the *minimum* cost in microcredits to publish the given execution (total cost, (storage cost, namespace cost)). diff --git a/synthesizer/src/vm/verify.rs b/synthesizer/src/vm/verify.rs index fe036b7e3a..fc4da0243c 100644 --- a/synthesizer/src/vm/verify.rs +++ b/synthesizer/src/vm/verify.rs @@ -146,7 +146,7 @@ impl> VM { bail!("Failed to compute the Merkle root for deployment transaction '{id}'") }; // Compute the deployment cost. - let (cost, _) = deployment_cost(deployment)?; + let cost = deployment_cost(deployment)?; // Ensure the fee is sufficient to cover the cost. if *fee.base_amount()? < cost { bail!("Transaction '{id}' has an insufficient base fee (deployment) - requires {cost} microcredits") From 5d3c2b7c244a225b24f9dd7fb42c8da73020033e Mon Sep 17 00:00:00 2001 From: Victor Sint Nicolaas Date: Wed, 10 Jan 2024 17:20:11 +0100 Subject: [PATCH 11/35] Nit: fix comment --- synthesizer/src/vm/helpers/cost.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synthesizer/src/vm/helpers/cost.rs b/synthesizer/src/vm/helpers/cost.rs index 4b69e5cd83..554eaeb4ae 100644 --- a/synthesizer/src/vm/helpers/cost.rs +++ b/synthesizer/src/vm/helpers/cost.rs @@ -45,7 +45,7 @@ pub fn deployment_cost(deployment: &Deployment) -> Result { .ok_or(anyhow!("The namespace cost computation overflowed for a deployment"))? .saturating_mul(1_000_000); // 1 microcredit = 1e-6 credits. - // Compute the synthesis cost in credits + // Compute the synthesis cost in microcredits. let synthesis_cost = num_constraints * N::SYNTH_FEE_MULTIPLIER; // Compute the total cost in microcredits. From 6b4b78bd32487819a577ce7f7a68cb2ebb950f4b Mon Sep 17 00:00:00 2001 From: raychu86 <14917648+raychu86@users.noreply.github.com> Date: Thu, 18 Jan 2024 18:48:16 -0800 Subject: [PATCH 12/35] Rewrite expectations --- .../expectations/vm/execute_and_finalize/test_rand.out | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) 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 b75f8b9875..01142551fb 100644 --- a/synthesizer/tests/expectations/vm/execute_and_finalize/test_rand.out +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/test_rand.out @@ -18,20 +18,15 @@ outputs: execute: test_rand.aleo/rand_chacha_check: outputs: - - '{"type":"future","id":"3721325135151760660773959530505944451747681933722462808964783147996869797702field","value":"{\n program_id: test_rand.aleo,\n function_name: rand_chacha_check,\n arguments: [\n 0field,\n false\n ]\n}"}' - speculate: the execution was rejected + - '{"type":"future","id":"488590592441422127997725386233571306457829570948543232762424611162021835080field","value":"{\n program_id: test_rand.aleo,\n function_name: rand_chacha_check,\n arguments: [\n 0field,\n false\n ]\n}"}' + speculate: the execution was accepted add_next_block: succeeded. - verified: true execute: test_rand.aleo/rand_chacha_check: outputs: -<<<<<<< HEAD - - '{"type":"future","id":"887371549615679800380522845098080464570119184210350810479392117984911457950field","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 rejected -======= - '{"type":"future","id":"884323248557348741020456011434839803868309861690594536593949575748229817915field","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 ->>>>>>> 6133fced4e3330b78e8eeda61050b8e815657f36 add_next_block: succeeded. additional: - child_outputs: From 722b6d0623125d41feb40cdb73ebe7bffa837b24 Mon Sep 17 00:00:00 2001 From: Howard Wu <9260812+howardwu@users.noreply.github.com> Date: Sun, 21 Jan 2024 13:09:30 -0800 Subject: [PATCH 13/35] Fix terminology, fix vulnerability --- circuit/environment/src/circuit.rs | 41 +++++++++---------- circuit/environment/src/environment.rs | 6 +-- circuit/network/src/v0.rs | 10 ++--- .../block/src/transaction/deployment/mod.rs | 18 ++++++-- synthesizer/process/src/stack/call/mod.rs | 2 +- synthesizer/process/src/stack/deploy.rs | 2 +- synthesizer/process/src/stack/execute.rs | 2 +- synthesizer/src/vm/helpers/cost.rs | 2 +- 8 files changed, 46 insertions(+), 37 deletions(-) diff --git a/circuit/environment/src/circuit.rs b/circuit/environment/src/circuit.rs index 505e33f16a..3fb5cc10ee 100644 --- a/circuit/environment/src/circuit.rs +++ b/circuit/environment/src/circuit.rs @@ -22,9 +22,9 @@ use core::{ type Field = ::Field; thread_local! { + pub(super) static CONSTRAINT_LIMIT: Cell> = Cell::new(None); pub(super) static CIRCUIT: RefCell> = RefCell::new(R1CS::new()); pub(super) static IN_WITNESS: Cell = Cell::new(false); - pub(super) static MAX_NUM_CONSTRAINTS: Cell = Cell::new(u64::MAX); pub(super) static ZERO: LinearCombination = LinearCombination::zero(); pub(super) static ONE: LinearCombination = LinearCombination::one(); } @@ -147,10 +147,12 @@ impl Environment for Circuit { // Ensure we are not in witness mode. if !in_witness.get() { CIRCUIT.with(|circuit| { - // Ensure we do not surpass maximum allowed number of constraints - MAX_NUM_CONSTRAINTS.with(|max_constraints| { - if circuit.borrow().num_constraints() >= max_constraints.get() { - Self::halt("Surpassing maximum allowed number of constraints") + // Ensure that we do not surpass the constraint limit for the circuit. + CONSTRAINT_LIMIT.with(|constraint_limit| { + if let Some(limit) = constraint_limit.get() { + if circuit.borrow().num_constraints() >= limit { + Self::halt(format!("Surpassed the constraint limit ({limit})")) + } } }); @@ -256,8 +258,11 @@ impl Environment for Circuit { panic!("{}", &error) } - /// TODO (howardwu): Abstraction - Refactor this into an appropriate design. - /// Circuits should not have easy access to this during synthesis. + /// Sets the constraint limit for the circuit. + fn set_constraint_limit(limit: Option) { + CONSTRAINT_LIMIT.with(|current_limit| current_limit.replace(limit)); + } + /// Returns the R1CS circuit, resetting the circuit. fn inject_r1cs(r1cs: R1CS) { CIRCUIT.with(|circuit| { @@ -276,15 +281,13 @@ impl Environment for Circuit { }) } - /// TODO (howardwu): Abstraction - Refactor this into an appropriate design. - /// Circuits should not have easy access to this during synthesis. /// Returns the R1CS circuit, resetting the circuit. fn eject_r1cs_and_reset() -> R1CS { CIRCUIT.with(|circuit| { // Reset the witness mode. IN_WITNESS.with(|in_witness| in_witness.replace(false)); - // Reset the max num constraints. - Self::set_constraint_maximum(u64::MAX); + // Reset the constraint limit. + Self::set_constraint_limit(None); // Eject the R1CS instance. let r1cs = circuit.replace(R1CS::<::BaseField>::new()); // Ensure the circuit is now empty. @@ -297,15 +300,13 @@ impl Environment for Circuit { }) } - /// TODO (howardwu): Abstraction - Refactor this into an appropriate design. - /// Circuits should not have easy access to this during synthesis. /// Returns the R1CS assignment of the circuit, resetting the circuit. fn eject_assignment_and_reset() -> Assignment<::Field> { CIRCUIT.with(|circuit| { // Reset the witness mode. IN_WITNESS.with(|in_witness| in_witness.replace(false)); - // Reset the num constraints. - Self::set_constraint_maximum(u64::MAX); + // Reset the constraint limit. + Self::set_constraint_limit(None); // Eject the R1CS instance. let r1cs = circuit.replace(R1CS::<::BaseField>::new()); assert_eq!(0, circuit.borrow().num_constants()); @@ -317,18 +318,14 @@ impl Environment for Circuit { }) } - /// Sets a maximum number of allowed constraints. - fn set_constraint_maximum(new_max_num_constraints: u64) { - MAX_NUM_CONSTRAINTS.with(|max_num_constraints| max_num_constraints.replace(new_max_num_constraints)); - } - /// Clears the circuit and initializes an empty environment. fn reset() { CIRCUIT.with(|circuit| { // Reset the witness mode. IN_WITNESS.with(|in_witness| in_witness.replace(false)); - // Reset the max num constraints. - Self::set_constraint_maximum(u64::MAX); + // Reset the constraint limit. + Self::set_constraint_limit(None); + // Reset the circuit. *circuit.borrow_mut() = R1CS::<::BaseField>::new(); assert_eq!(0, circuit.borrow().num_constants()); assert_eq!(1, circuit.borrow().num_public()); diff --git a/circuit/environment/src/environment.rs b/circuit/environment/src/environment.rs index 789cfd61a2..f621611cde 100644 --- a/circuit/environment/src/environment.rs +++ b/circuit/environment/src/environment.rs @@ -160,6 +160,9 @@ pub trait Environment: 'static + Copy + Clone + fmt::Debug + fmt::Display + Eq + ::halt(message) } + /// Sets the constraint limit for the circuit. + fn set_constraint_limit(limit: Option); + /// Returns the R1CS circuit, resetting the circuit. fn inject_r1cs(r1cs: R1CS); @@ -169,9 +172,6 @@ pub trait Environment: 'static + Copy + Clone + fmt::Debug + fmt::Display + Eq + /// Returns the R1CS assignment of the circuit, resetting the circuit. fn eject_assignment_and_reset() -> Assignment<::Field>; - /// Sets a maximum amount of allowed constraints - fn set_constraint_maximum(new_max_num_constraints: u64); - /// Clears and initializes an empty environment. fn reset(); } diff --git a/circuit/network/src/v0.rs b/circuit/network/src/v0.rs index 7f76af350e..9fd219d090 100644 --- a/circuit/network/src/v0.rs +++ b/circuit/network/src/v0.rs @@ -466,6 +466,11 @@ impl Environment for AleoV0 { E::halt(message) } + /// Sets the constraint limit for the circuit. + fn set_constraint_limit(limit: Option) { + E::set_constraint_limit(limit) + } + /// Returns the R1CS circuit, resetting the circuit. fn inject_r1cs(r1cs: R1CS) { E::inject_r1cs(r1cs) @@ -481,11 +486,6 @@ impl Environment for AleoV0 { E::eject_assignment_and_reset() } - /// Sets a maximum amount of allowed constraints - fn set_constraint_maximum(new_max_num_constraints: u64) { - E::set_constraint_maximum(new_max_num_constraints) - } - /// Clears the circuit and initializes an empty environment. fn reset() { E::reset() diff --git a/ledger/block/src/transaction/deployment/mod.rs b/ledger/block/src/transaction/deployment/mod.rs index c253c66ec2..fbea825444 100644 --- a/ledger/block/src/transaction/deployment/mod.rs +++ b/ledger/block/src/transaction/deployment/mod.rs @@ -124,9 +124,21 @@ impl Deployment { &self.verifying_keys } - /// Returns the total number of constraints. - pub fn num_constraints(&self) -> u64 { - self.verifying_keys.iter().map(|(_, (vk, _))| vk.circuit_info.num_constraints as u64).sum::() + /// Returns the sum of the constraint counts for all functions in this deployment. + pub fn num_combined_constraints(&self) -> Result { + // Initialize the accumulator. + let mut num_combined_constraints = 0u64; + // Iterate over the functions. + for (_, (vk, _)) in &self.verifying_keys { + // Add the number of constraints. + // Note: This method must be *checked* because the claimed constraint count + // is from the user, not the synthesizer. + num_combined_constraints = num_combined_constraints + .checked_add(vk.circuit_info.num_constraints as u64) + .ok_or_else(|| anyhow!("Overflow when counting constraints for '{}'", self.program_id()))?; + } + // Return the number of combined constraints. + Ok(num_combined_constraints) } /// Returns the deployment ID. diff --git a/synthesizer/process/src/stack/call/mod.rs b/synthesizer/process/src/stack/call/mod.rs index 610942eccc..56a0689c8e 100644 --- a/synthesizer/process/src/stack/call/mod.rs +++ b/synthesizer/process/src/stack/call/mod.rs @@ -353,7 +353,7 @@ impl CallTrait for Call { // If the circuit is in CheckDeployment mode, set a constraint maximum. if let CallStack::CheckDeployment(_, _, _, num_constraints) = ®isters.call_stack() { - A::set_constraint_maximum(*num_constraints); + A::set_constraint_limit(Some(*num_constraints)); } use circuit::Inject; diff --git a/synthesizer/process/src/stack/deploy.rs b/synthesizer/process/src/stack/deploy.rs index 261e282cd4..d3d6e1321e 100644 --- a/synthesizer/process/src/stack/deploy.rs +++ b/synthesizer/process/src/stack/deploy.rs @@ -74,7 +74,7 @@ impl Stack { let program_id = self.program.id(); // Check that the deployment does not require too many constraints - let total_num_constraints = deployment.num_constraints(); + let total_num_constraints = deployment.num_combined_constraints()?; ensure!(total_num_constraints <= N::MAX_DEPLOYMENT_CONSTRAINTS); // Construct the call stacks and assignments used to verify the certificates. diff --git a/synthesizer/process/src/stack/execute.rs b/synthesizer/process/src/stack/execute.rs index f54dd5611c..48546443db 100644 --- a/synthesizer/process/src/stack/execute.rs +++ b/synthesizer/process/src/stack/execute.rs @@ -144,7 +144,7 @@ impl StackExecute for Stack { // If the circuit is in CheckDeployment mode, set a constraint maximum. if let CallStack::CheckDeployment(_, _, _, num_constraints) = &call_stack { - A::set_constraint_maximum(*num_constraints); + A::set_constraint_limit(Some(*num_constraints)); } // Retrieve the next request. diff --git a/synthesizer/src/vm/helpers/cost.rs b/synthesizer/src/vm/helpers/cost.rs index 554eaeb4ae..7a1ab03b4c 100644 --- a/synthesizer/src/vm/helpers/cost.rs +++ b/synthesizer/src/vm/helpers/cost.rs @@ -32,7 +32,7 @@ pub fn deployment_cost(deployment: &Deployment) -> Result { // Determine the number of characters in the program ID. let num_characters = u32::try_from(program_id.name().to_string().len())?; // Determine the number of constraints in the program - let num_constraints = deployment.num_constraints(); + let num_constraints = deployment.num_combined_constraints()?; // Compute the storage cost in microcredits. let storage_cost = size_in_bytes From a22ab6da66ba111732476c20fe451abddbb32386 Mon Sep 17 00:00:00 2001 From: Howard Wu <9260812+howardwu@users.noreply.github.com> Date: Sun, 21 Jan 2024 13:13:58 -0800 Subject: [PATCH 14/35] Fix the deployment limit usage --- synthesizer/process/src/stack/call/mod.rs | 2 +- synthesizer/process/src/stack/deploy.rs | 4 +--- synthesizer/process/src/stack/execute.rs | 2 +- synthesizer/process/src/stack/mod.rs | 2 +- synthesizer/process/src/tests/test_credits.rs | 5 +---- 5 files changed, 5 insertions(+), 10 deletions(-) diff --git a/synthesizer/process/src/stack/call/mod.rs b/synthesizer/process/src/stack/call/mod.rs index 56a0689c8e..87f514e180 100644 --- a/synthesizer/process/src/stack/call/mod.rs +++ b/synthesizer/process/src/stack/call/mod.rs @@ -353,7 +353,7 @@ impl CallTrait for Call { // If the circuit is in CheckDeployment mode, set a constraint maximum. if let CallStack::CheckDeployment(_, _, _, num_constraints) = ®isters.call_stack() { - A::set_constraint_limit(Some(*num_constraints)); + A::set_constraint_limit(*num_constraints); } use circuit::Inject; diff --git a/synthesizer/process/src/stack/deploy.rs b/synthesizer/process/src/stack/deploy.rs index d3d6e1321e..6ca8bb3c94 100644 --- a/synthesizer/process/src/stack/deploy.rs +++ b/synthesizer/process/src/stack/deploy.rs @@ -123,8 +123,6 @@ impl Stack { rng, )?; lap!(timer, "Compute the request for {}", function.name()); - // Get the expected number of constraints - let expected_num_constraints = verifying_key.circuit_info.num_constraints as u64; // Initialize the assignments. let assignments = Assignments::::default(); // Initialize the call stack. @@ -132,7 +130,7 @@ impl Stack { vec![request], burner_private_key, assignments.clone(), - expected_num_constraints, + Some(verifying_key.circuit_info.num_constraints as u64), ); // Append the function name, callstack, and assignments. call_stacks.push((function.name(), call_stack, assignments)); diff --git a/synthesizer/process/src/stack/execute.rs b/synthesizer/process/src/stack/execute.rs index 48546443db..e3a2b31f02 100644 --- a/synthesizer/process/src/stack/execute.rs +++ b/synthesizer/process/src/stack/execute.rs @@ -144,7 +144,7 @@ impl StackExecute for Stack { // If the circuit is in CheckDeployment mode, set a constraint maximum. if let CallStack::CheckDeployment(_, _, _, num_constraints) = &call_stack { - A::set_constraint_limit(Some(*num_constraints)); + A::set_constraint_limit(*num_constraints); } // Retrieve the next request. diff --git a/synthesizer/process/src/stack/mod.rs b/synthesizer/process/src/stack/mod.rs index 33e608865b..e0edca4922 100644 --- a/synthesizer/process/src/stack/mod.rs +++ b/synthesizer/process/src/stack/mod.rs @@ -81,7 +81,7 @@ pub type Assignments = Arc pub enum CallStack { Authorize(Vec>, PrivateKey, Authorization), Synthesize(Vec>, PrivateKey, Authorization), - CheckDeployment(Vec>, PrivateKey, Assignments, u64), + CheckDeployment(Vec>, PrivateKey, Assignments, Option), Evaluate(Authorization), Execute(Authorization, Arc>>), PackageRun(Vec>, PrivateKey, Assignments), diff --git a/synthesizer/process/src/tests/test_credits.rs b/synthesizer/process/src/tests/test_credits.rs index c7464e46e4..f7b0671081 100644 --- a/synthesizer/process/src/tests/test_credits.rs +++ b/synthesizer/process/src/tests/test_credits.rs @@ -1527,11 +1527,8 @@ mod sanity_checks { Request::sign(private_key, program_id, function_name, inputs.iter(), &input_types, is_root, rng).unwrap(); // Initialize the assignments. let assignments = Assignments::::default(); - // Set max num constraints - let max_num_constraints = u64::MAX; // Initialize the call stack. - let call_stack = - CallStack::CheckDeployment(vec![request], *private_key, assignments.clone(), max_num_constraints); + let call_stack = CallStack::CheckDeployment(vec![request], *private_key, assignments.clone(), None); // Synthesize the circuit. let _response = stack.execute_function::(call_stack, None, rng).unwrap(); // Retrieve the assignment. From dc8fb722eed89b20e3f23e9dcc864b7d61e3a8f6 Mon Sep 17 00:00:00 2001 From: Howard Wu <9260812+howardwu@users.noreply.github.com> Date: Sun, 21 Jan 2024 13:17:01 -0800 Subject: [PATCH 15/35] Write like an adult --- synthesizer/src/vm/deploy.rs | 2 +- synthesizer/src/vm/helpers/cost.rs | 6 +++--- synthesizer/src/vm/mod.rs | 4 ++-- synthesizer/src/vm/verify.rs | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/synthesizer/src/vm/deploy.rs b/synthesizer/src/vm/deploy.rs index f548607d52..e080ba54d9 100644 --- a/synthesizer/src/vm/deploy.rs +++ b/synthesizer/src/vm/deploy.rs @@ -40,7 +40,7 @@ impl> VM { let owner = ProgramOwner::new(private_key, deployment_id, rng)?; // Compute the minimum deployment cost. - let minimum_deployment_cost = deployment_cost(&deployment)?; + let (minimum_deployment_cost, (_, _)) = deployment_cost(&deployment)?; // Authorize the fee. let fee_authorization = match fee_record { Some(record) => self.authorize_fee_private( diff --git a/synthesizer/src/vm/helpers/cost.rs b/synthesizer/src/vm/helpers/cost.rs index 7a1ab03b4c..1c3625814d 100644 --- a/synthesizer/src/vm/helpers/cost.rs +++ b/synthesizer/src/vm/helpers/cost.rs @@ -23,8 +23,8 @@ use synthesizer_program::{Command, Finalize, Instruction}; use std::collections::HashMap; -/// Returns the *minimum* cost in microcredits to publish the given deployment total cost. -pub fn deployment_cost(deployment: &Deployment) -> Result { +/// Returns the *minimum* cost in microcredits to publish the given deployment (total cost, (storage cost, namespace cost)). +pub fn deployment_cost(deployment: &Deployment) -> Result<(u64, (u64, u64))> { // Determine the number of bytes in the deployment. let size_in_bytes = deployment.size_in_bytes()?; // Retrieve the program ID. @@ -54,7 +54,7 @@ pub fn deployment_cost(deployment: &Deployment) -> Result { .and_then(|x| x.checked_add(synthesis_cost)) .ok_or(anyhow!("The total cost computation overflowed for a deployment"))?; - Ok(total_cost) + Ok((total_cost, (storage_cost, namespace_cost))) } /// Returns the *minimum* cost in microcredits to publish the given execution (total cost, (storage cost, namespace cost)). diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index 57ee90e152..70469befc1 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -1109,10 +1109,10 @@ function do: ) .unwrap(); - // Create the Deployment Transaction + // 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 constraints. + // Verify the deployment transaction. It should fail because there are too many constraints. assert!(vm.check_transaction(&deployment, None, rng).is_err()); } diff --git a/synthesizer/src/vm/verify.rs b/synthesizer/src/vm/verify.rs index e39717d8be..e1a7c05330 100644 --- a/synthesizer/src/vm/verify.rs +++ b/synthesizer/src/vm/verify.rs @@ -146,7 +146,7 @@ impl> VM { bail!("Failed to compute the Merkle root for deployment transaction '{id}'") }; // Compute the deployment cost. - let cost = deployment_cost(deployment)?; + let (cost, _) = deployment_cost(deployment)?; // Ensure the fee is sufficient to cover the cost. if *fee.base_amount()? < cost { bail!("Transaction '{id}' has an insufficient base fee (deployment) - requires {cost} microcredits") From e919f7b655819938710ee1aae0833c1dcf340a27 Mon Sep 17 00:00:00 2001 From: Howard Wu <9260812+howardwu@users.noreply.github.com> Date: Sun, 21 Jan 2024 13:20:24 -0800 Subject: [PATCH 16/35] nit: comments --- synthesizer/process/src/stack/call/mod.rs | 6 +++--- synthesizer/process/src/stack/execute.rs | 6 +++--- synthesizer/process/src/stack/mod.rs | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/synthesizer/process/src/stack/call/mod.rs b/synthesizer/process/src/stack/call/mod.rs index 87f514e180..f0e9b18797 100644 --- a/synthesizer/process/src/stack/call/mod.rs +++ b/synthesizer/process/src/stack/call/mod.rs @@ -351,9 +351,9 @@ impl CallTrait for Call { // Inject the existing circuit. A::inject_r1cs(r1cs); - // If the circuit is in CheckDeployment mode, set a constraint maximum. - if let CallStack::CheckDeployment(_, _, _, num_constraints) = ®isters.call_stack() { - A::set_constraint_limit(*num_constraints); + // If in 'CheckDeployment' mode, set the expected constraint limit. + if let CallStack::CheckDeployment(_, _, _, constraint_limit) = ®isters.call_stack() { + A::set_constraint_limit(*constraint_limit); } use circuit::Inject; diff --git a/synthesizer/process/src/stack/execute.rs b/synthesizer/process/src/stack/execute.rs index e3a2b31f02..11954e54f0 100644 --- a/synthesizer/process/src/stack/execute.rs +++ b/synthesizer/process/src/stack/execute.rs @@ -142,9 +142,9 @@ impl StackExecute for Stack { // Ensure the circuit environment is clean. A::reset(); - // If the circuit is in CheckDeployment mode, set a constraint maximum. - if let CallStack::CheckDeployment(_, _, _, num_constraints) = &call_stack { - A::set_constraint_limit(*num_constraints); + // If in 'CheckDeployment' mode, set the constraint limit. + if let CallStack::CheckDeployment(_, _, _, constraint_limit) = &call_stack { + A::set_constraint_limit(*constraint_limit); } // Retrieve the next request. diff --git a/synthesizer/process/src/stack/mod.rs b/synthesizer/process/src/stack/mod.rs index e0edca4922..e354351093 100644 --- a/synthesizer/process/src/stack/mod.rs +++ b/synthesizer/process/src/stack/mod.rs @@ -109,12 +109,12 @@ impl CallStack { CallStack::Synthesize(requests, private_key, authorization) => { CallStack::Synthesize(requests.clone(), *private_key, authorization.replicate()) } - CallStack::CheckDeployment(requests, private_key, assignments, num_constraints) => { + CallStack::CheckDeployment(requests, private_key, assignments, constraint_limit) => { CallStack::CheckDeployment( requests.clone(), *private_key, Arc::new(RwLock::new(assignments.read().clone())), - *num_constraints, + *constraint_limit, ) } CallStack::Evaluate(authorization) => CallStack::Evaluate(authorization.replicate()), From d2e52b0188c4eb78a10b4e06546c68547971af6f Mon Sep 17 00:00:00 2001 From: Howard Wu <9260812+howardwu@users.noreply.github.com> Date: Sun, 21 Jan 2024 13:26:12 -0800 Subject: [PATCH 17/35] Update comments --- console/network/src/lib.rs | 2 +- synthesizer/process/src/stack/deploy.rs | 5 ++--- synthesizer/src/vm/helpers/cost.rs | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/console/network/src/lib.rs b/console/network/src/lib.rs index 69807dc6db..313c34e5f0 100644 --- a/console/network/src/lib.rs +++ b/console/network/src/lib.rs @@ -101,7 +101,7 @@ pub trait Network: /// The cost in microcredits per constraint for the deployment transaction. const SYNTH_FEE_MULTIPLIER: u64 = 25; // 25 microcredit per constraint to synthesize /// The maximum number of constraints in a deployment - const MAX_DEPLOYMENT_CONSTRAINTS: u64 = 1_000_000; + const MAX_DEPLOYMENT_LIMIT: u64 = 1_000_000; /// The maximum number of microcredits that can be spent as a fee. const MAX_FEE: u64 = 1_000_000_000_000_000; diff --git a/synthesizer/process/src/stack/deploy.rs b/synthesizer/process/src/stack/deploy.rs index 6ca8bb3c94..230c3e5eaf 100644 --- a/synthesizer/process/src/stack/deploy.rs +++ b/synthesizer/process/src/stack/deploy.rs @@ -73,9 +73,8 @@ impl Stack { let program_id = self.program.id(); - // Check that the deployment does not require too many constraints - let total_num_constraints = deployment.num_combined_constraints()?; - ensure!(total_num_constraints <= N::MAX_DEPLOYMENT_CONSTRAINTS); + // Check that the number of combined constraints does not exceed the deployment limit. + ensure!(deployment.num_combined_constraints()? <= N::MAX_DEPLOYMENT_LIMIT); // Construct the call stacks and assignments used to verify the certificates. let mut call_stacks = Vec::with_capacity(deployment.verifying_keys().len()); diff --git a/synthesizer/src/vm/helpers/cost.rs b/synthesizer/src/vm/helpers/cost.rs index 1c3625814d..895b6a8480 100644 --- a/synthesizer/src/vm/helpers/cost.rs +++ b/synthesizer/src/vm/helpers/cost.rs @@ -31,7 +31,7 @@ pub fn deployment_cost(deployment: &Deployment) -> Result<(u64, ( let program_id = deployment.program_id(); // Determine the number of characters in the program ID. let num_characters = u32::try_from(program_id.name().to_string().len())?; - // Determine the number of constraints in the program + // Compute the number of combined constraints in the program. let num_constraints = deployment.num_combined_constraints()?; // Compute the storage cost in microcredits. From 1568ce05eb72230569a3dce8e40a8e69886b8592 Mon Sep 17 00:00:00 2001 From: Howard Wu <9260812+howardwu@users.noreply.github.com> Date: Sun, 21 Jan 2024 13:27:45 -0800 Subject: [PATCH 18/35] Adds a getter for the constraint limit from the circuit --- circuit/environment/src/circuit.rs | 5 +++++ circuit/environment/src/environment.rs | 3 +++ circuit/network/src/v0.rs | 5 +++++ 3 files changed, 13 insertions(+) diff --git a/circuit/environment/src/circuit.rs b/circuit/environment/src/circuit.rs index 3fb5cc10ee..8e0788d01e 100644 --- a/circuit/environment/src/circuit.rs +++ b/circuit/environment/src/circuit.rs @@ -258,6 +258,11 @@ impl Environment for Circuit { panic!("{}", &error) } + /// Returns the constraint limit for the circuit, if one exists. + fn get_constraint_limit() -> Option { + CONSTRAINT_LIMIT.with(|current_limit| current_limit.get()) + } + /// Sets the constraint limit for the circuit. fn set_constraint_limit(limit: Option) { CONSTRAINT_LIMIT.with(|current_limit| current_limit.replace(limit)); diff --git a/circuit/environment/src/environment.rs b/circuit/environment/src/environment.rs index f621611cde..d8fdc5a181 100644 --- a/circuit/environment/src/environment.rs +++ b/circuit/environment/src/environment.rs @@ -160,6 +160,9 @@ pub trait Environment: 'static + Copy + Clone + fmt::Debug + fmt::Display + Eq + ::halt(message) } + /// Returns the constraint limit for the circuit, if one exists. + fn get_constraint_limit() -> Option; + /// Sets the constraint limit for the circuit. fn set_constraint_limit(limit: Option); diff --git a/circuit/network/src/v0.rs b/circuit/network/src/v0.rs index 9fd219d090..936e1e581c 100644 --- a/circuit/network/src/v0.rs +++ b/circuit/network/src/v0.rs @@ -466,6 +466,11 @@ impl Environment for AleoV0 { E::halt(message) } + /// Returns the constraint limit for the circuit, if one exists. + fn get_constraint_limit() -> Option { + E::get_constraint_limit() + } + /// Sets the constraint limit for the circuit. fn set_constraint_limit(limit: Option) { E::set_constraint_limit(limit) From e39f9d27e8e211d27e206e531fe496a117c6519d Mon Sep 17 00:00:00 2001 From: Howard Wu <9260812+howardwu@users.noreply.github.com> Date: Sun, 21 Jan 2024 13:29:34 -0800 Subject: [PATCH 19/35] Fix names, set limit to 1<<20 --- console/network/src/lib.rs | 4 ++-- synthesizer/src/vm/helpers/cost.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/console/network/src/lib.rs b/console/network/src/lib.rs index 313c34e5f0..80e53c627e 100644 --- a/console/network/src/lib.rs +++ b/console/network/src/lib.rs @@ -99,9 +99,9 @@ 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 SYNTH_FEE_MULTIPLIER: u64 = 25; // 25 microcredit per constraint to synthesize + const SYNTHESIS_FEE_MULTIPLIER: u64 = 25; // 25 microcredits per constraint /// The maximum number of constraints in a deployment - const MAX_DEPLOYMENT_LIMIT: u64 = 1_000_000; + const MAX_DEPLOYMENT_LIMIT: u64 = 1 << 20; // 1,048,576 constraints /// The maximum number of microcredits that can be spent as a fee. const MAX_FEE: u64 = 1_000_000_000_000_000; diff --git a/synthesizer/src/vm/helpers/cost.rs b/synthesizer/src/vm/helpers/cost.rs index 895b6a8480..88218c9a86 100644 --- a/synthesizer/src/vm/helpers/cost.rs +++ b/synthesizer/src/vm/helpers/cost.rs @@ -46,7 +46,7 @@ pub fn deployment_cost(deployment: &Deployment) -> Result<(u64, ( .saturating_mul(1_000_000); // 1 microcredit = 1e-6 credits. // Compute the synthesis cost in microcredits. - let synthesis_cost = num_constraints * N::SYNTH_FEE_MULTIPLIER; + let synthesis_cost = num_constraints * N::SYNTHESIS_FEE_MULTIPLIER; // Compute the total cost in microcredits. let total_cost = storage_cost From 438351807ba2b9eda9cbd2b89b0de64e2cb56c87 Mon Sep 17 00:00:00 2001 From: Howard Wu <9260812+howardwu@users.noreply.github.com> Date: Sun, 21 Jan 2024 13:30:14 -0800 Subject: [PATCH 20/35] Missing period (.) --- console/network/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/console/network/src/lib.rs b/console/network/src/lib.rs index 80e53c627e..4995a5f577 100644 --- a/console/network/src/lib.rs +++ b/console/network/src/lib.rs @@ -100,7 +100,7 @@ pub trait Network: 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 - /// The maximum number of constraints in a deployment + /// The maximum number of constraints in a deployment. const MAX_DEPLOYMENT_LIMIT: u64 = 1 << 20; // 1,048,576 constraints /// The maximum number of microcredits that can be spent as a fee. const MAX_FEE: u64 = 1_000_000_000_000_000; From 3841a3e492b2f13123bd7a5ef0c7404fa39fd176 Mon Sep 17 00:00:00 2001 From: Howard Wu <9260812+howardwu@users.noreply.github.com> Date: Sun, 21 Jan 2024 13:37:59 -0800 Subject: [PATCH 21/35] Include the synthesis cost in the return for the deployment cost --- synthesizer/src/vm/deploy.rs | 2 +- synthesizer/src/vm/helpers/cost.rs | 20 ++++++++++---------- synthesizer/src/vm/verify.rs | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/synthesizer/src/vm/deploy.rs b/synthesizer/src/vm/deploy.rs index e080ba54d9..f9e260793c 100644 --- a/synthesizer/src/vm/deploy.rs +++ b/synthesizer/src/vm/deploy.rs @@ -40,7 +40,7 @@ impl> VM { let owner = ProgramOwner::new(private_key, deployment_id, rng)?; // Compute the minimum deployment cost. - let (minimum_deployment_cost, (_, _)) = deployment_cost(&deployment)?; + let (minimum_deployment_cost, _) = deployment_cost(&deployment)?; // Authorize the fee. let fee_authorization = match fee_record { Some(record) => self.authorize_fee_private( diff --git a/synthesizer/src/vm/helpers/cost.rs b/synthesizer/src/vm/helpers/cost.rs index 88218c9a86..6f94607978 100644 --- a/synthesizer/src/vm/helpers/cost.rs +++ b/synthesizer/src/vm/helpers/cost.rs @@ -23,8 +23,8 @@ use synthesizer_program::{Command, Finalize, Instruction}; use std::collections::HashMap; -/// Returns the *minimum* cost in microcredits to publish the given deployment (total cost, (storage cost, namespace cost)). -pub fn deployment_cost(deployment: &Deployment) -> Result<(u64, (u64, u64))> { +/// Returns the *minimum* cost in microcredits to publish the given deployment (total cost, (storage cost, synthesis cost, namespace cost)). +pub fn deployment_cost(deployment: &Deployment) -> Result<(u64, (u64, u64, u64))> { // Determine the number of bytes in the deployment. let size_in_bytes = deployment.size_in_bytes()?; // Retrieve the program ID. @@ -32,32 +32,32 @@ pub fn deployment_cost(deployment: &Deployment) -> Result<(u64, ( // Determine the number of characters in the program ID. let num_characters = u32::try_from(program_id.name().to_string().len())?; // Compute the number of combined constraints in the program. - let num_constraints = deployment.num_combined_constraints()?; + let num_combined_constraints = deployment.num_combined_constraints()?; // Compute the storage cost in microcredits. let storage_cost = size_in_bytes .checked_mul(N::DEPLOYMENT_FEE_MULTIPLIER) .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; + // Compute the namespace cost in credits: 10^(10 - num_characters). let namespace_cost = 10u64 .checked_pow(10u32.saturating_sub(num_characters)) .ok_or(anyhow!("The namespace cost computation overflowed for a deployment"))? .saturating_mul(1_000_000); // 1 microcredit = 1e-6 credits. - // Compute the synthesis cost in microcredits. - let synthesis_cost = num_constraints * N::SYNTHESIS_FEE_MULTIPLIER; - // Compute the total cost in microcredits. let total_cost = storage_cost - .checked_add(namespace_cost) - .and_then(|x| x.checked_add(synthesis_cost)) + .checked_add(synthesis_cost) + .and_then(|x| x.checked_add(namespace_cost)) .ok_or(anyhow!("The total cost computation overflowed for a deployment"))?; - Ok((total_cost, (storage_cost, namespace_cost))) + Ok((total_cost, (storage_cost, synthesis_cost, namespace_cost))) } -/// Returns the *minimum* cost in microcredits to publish the given execution (total cost, (storage cost, namespace cost)). +/// Returns the *minimum* cost in microcredits to publish the given execution (total cost, (storage cost, finalize cost)). pub fn execution_cost>( vm: &VM, execution: &Execution, diff --git a/synthesizer/src/vm/verify.rs b/synthesizer/src/vm/verify.rs index e1a7c05330..6fc47523a5 100644 --- a/synthesizer/src/vm/verify.rs +++ b/synthesizer/src/vm/verify.rs @@ -145,7 +145,7 @@ impl> VM { let Ok(deployment_id) = deployment.to_deployment_id() else { bail!("Failed to compute the Merkle root for deployment transaction '{id}'") }; - // Compute the deployment cost. + // Compute the minimum deployment cost. let (cost, _) = deployment_cost(deployment)?; // Ensure the fee is sufficient to cover the cost. if *fee.base_amount()? < cost { From 5d0c2da4a54f98b01a912b3f37cfafa7b6512cf6 Mon Sep 17 00:00:00 2001 From: Howard Wu <9260812+howardwu@users.noreply.github.com> Date: Sun, 21 Jan 2024 13:41:38 -0800 Subject: [PATCH 22/35] Add enforcement that the number of synthesized constraints matches the declared count --- synthesizer/process/src/stack/execute.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/synthesizer/process/src/stack/execute.rs b/synthesizer/process/src/stack/execute.rs index 11954e54f0..b0c6a37a0d 100644 --- a/synthesizer/process/src/stack/execute.rs +++ b/synthesizer/process/src/stack/execute.rs @@ -421,7 +421,15 @@ impl StackExecute for Stack { 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, constraint_limit) = registers.call_stack() { + // Ensure the assignment matches the constraint limit. + if let Some(constraint_limit) = constraint_limit { + ensure!( + assignment.num_constraints() == constraint_limit, + "The synthesized number of constraints ({}) does not match the declared limit in the verifying key ({constraint_limit})", + assignment.num_constraints(), + ); + } // Construct the call metrics. let metrics = CallMetrics { program_id: *self.program_id(), From 01b95d213e97514364387f23981f2e9814ac7506 Mon Sep 17 00:00:00 2001 From: Victor Sint Nicolaas Date: Sun, 21 Jan 2024 17:35:42 -0800 Subject: [PATCH 23/35] WIP: scaffolding for testing vk manipulation --- synthesizer/src/vm/mod.rs | 126 +++++++++++++++++++++++++++++++++++++- 1 file changed, 125 insertions(+), 1 deletion(-) diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index 70469befc1..c4f8108981 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -374,6 +374,7 @@ pub(crate) mod test_helpers { use indexmap::IndexMap; use once_cell::sync::OnceCell; use std::borrow::Borrow; + use synthesizer_snark::VerifyingKey; pub(crate) type CurrentNetwork = Testnet3; @@ -1100,7 +1101,7 @@ function check: // Deploy the base program. let program = Program::from_str( r" -program program_layer_0.aleo; +program synthesis_overload.aleo; function do: input r0 as [[u128; 32u32]; 2u32].private; @@ -1116,6 +1117,129 @@ function do: assert!(vm.check_transaction(&deployment, None, rng).is_err()); } + #[test] + fn test_deployment_synthesis_overreport() { + 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_overreport.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"); + }; + + // 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() { + 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()))); + } + + // Compute the required fee. + let required_fee = *fee.base_amount().unwrap() + 1000; // TODO: calculate exact + // Authorize a new fee. + let fee_authorization = vm + .authorize_fee_public(&private_key, required_fee, 0, deployment.as_ref().to_deployment_id().unwrap(), rng) + .unwrap(); + // Compute the fee. + let fee = vm.execute_fee_authorization(fee_authorization, None, rng).unwrap(); + + // Create a new deployment tranasction with the overreported verifying keys. + let adjusted_deployment = + Deployment::new(deployment.edition(), deployment.program().clone(), vks_with_overreport).unwrap(); + let adjusted_transaction = Transaction::Deploy(txid, program_owner, Box::new(adjusted_deployment), fee); + + // Verify the deployment transaction. It should fail because the num_constraints in the vk are not correct. + let res = vm.check_transaction(&adjusted_transaction, None, rng); + println!("Result of check transaction: {:?}", res); + assert!(vm.check_transaction(&adjusted_transaction, None, rng).is_err()); + + println!("Test if we reach this code"); + } + + #[test] + fn test_deployment_synthesis_underreport() { + 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_overreport.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 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() { + let mut vk = vk.deref().clone(); + vk.circuit_info.num_constraints -= 1; + let vk = VerifyingKey::new(Arc::new(vk)); + vks_with_underreport.push((*id, (vk, cert.clone()))); + } + + // Create a new deployment tranasction 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 fail because the num_constraints in the vk are not correct. + let res = vm.check_transaction(&adjusted_transaction, None, rng); + println!("Result of check transaction: {:?}", res); + assert!(vm.check_transaction(&adjusted_transaction, None, rng).is_err()); + + println!("Test if we reach this code"); + } + #[test] #[ignore] fn test_deployment_memory_overload() { From cc87b9896d2fe71dc3d68505372536a435844169 Mon Sep 17 00:00:00 2001 From: evan-schott <53463459+evan-schott@users.noreply.github.com> Date: Tue, 30 Jan 2024 22:21:10 -0800 Subject: [PATCH 24/35] Remove redundant check --- synthesizer/process/src/stack/execute.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/synthesizer/process/src/stack/execute.rs b/synthesizer/process/src/stack/execute.rs index b0c6a37a0d..11954e54f0 100644 --- a/synthesizer/process/src/stack/execute.rs +++ b/synthesizer/process/src/stack/execute.rs @@ -421,15 +421,7 @@ impl StackExecute for Stack { lap!(timer, "Save the transition"); } // If the circuit is in `CheckDeployment` mode, then save the assignment. - else if let CallStack::CheckDeployment(_, _, ref assignments, constraint_limit) = registers.call_stack() { - // Ensure the assignment matches the constraint limit. - if let Some(constraint_limit) = constraint_limit { - ensure!( - assignment.num_constraints() == constraint_limit, - "The synthesized number of constraints ({}) does not match the declared limit in the verifying key ({constraint_limit})", - assignment.num_constraints(), - ); - } + else if let CallStack::CheckDeployment(_, _, ref assignments, _) = registers.call_stack() { // Construct the call metrics. let metrics = CallMetrics { program_id: *self.program_id(), From 1b02d5c1784d400291029226c8bbb7f5e1144c7e Mon Sep 17 00:00:00 2001 From: evan-schott <53463459+evan-schott@users.noreply.github.com> Date: Tue, 30 Jan 2024 22:21:30 -0800 Subject: [PATCH 25/35] Revise tests --- synthesizer/src/vm/mod.rs | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index c4f8108981..622420beeb 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -1148,7 +1148,7 @@ function do: 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 { + let Transaction::Deploy(_, program_owner, deployment, fee) = transaction else { panic!("Expected a deployment transaction"); }; @@ -1161,8 +1161,8 @@ function do: vks_with_overreport.push((*id, (vk, cert.clone()))); } - // Compute the required fee. - let required_fee = *fee.base_amount().unwrap() + 1000; // TODO: calculate exact + // Each additional constraint costs 25 microcredits, so we need to increase the fee by 25 microcredits. + let required_fee = *fee.base_amount().unwrap() + 25; // Authorize a new fee. let fee_authorization = vm .authorize_fee_public(&private_key, required_fee, 0, deployment.as_ref().to_deployment_id().unwrap(), rng) @@ -1170,17 +1170,12 @@ function do: // Compute the fee. let fee = vm.execute_fee_authorization(fee_authorization, None, rng).unwrap(); - // Create a new deployment tranasction with the overreported verifying keys. + // Create a new deployment transaction with the overreported verifying keys. let adjusted_deployment = Deployment::new(deployment.edition(), deployment.program().clone(), vks_with_overreport).unwrap(); - let adjusted_transaction = Transaction::Deploy(txid, program_owner, Box::new(adjusted_deployment), fee); + let adjusted_transaction = Transaction::from_deployment(program_owner, adjusted_deployment, fee).unwrap(); - // Verify the deployment transaction. It should fail because the num_constraints in the vk are not correct. - let res = vm.check_transaction(&adjusted_transaction, None, rng); - println!("Result of check transaction: {:?}", res); assert!(vm.check_transaction(&adjusted_transaction, None, rng).is_err()); - - println!("Test if we reach this code"); } #[test] @@ -1227,17 +1222,13 @@ function do: vks_with_underreport.push((*id, (vk, cert.clone()))); } - // Create a new deployment tranasction with the underreported verifying keys. + // 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 fail because the num_constraints in the vk are not correct. - let res = vm.check_transaction(&adjusted_transaction, None, rng); - println!("Result of check transaction: {:?}", res); assert!(vm.check_transaction(&adjusted_transaction, None, rng).is_err()); - - println!("Test if we reach this code"); } #[test] From 1ae6626c99de846445c45350b73678714f30e3ad Mon Sep 17 00:00:00 2001 From: evan-schott <53463459+evan-schott@users.noreply.github.com> Date: Thu, 1 Feb 2024 15:51:51 -0500 Subject: [PATCH 26/35] Modify constraint limit to account for the constraint added after synthesis that randomizes vars --- synthesizer/process/src/stack/deploy.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/synthesizer/process/src/stack/deploy.rs b/synthesizer/process/src/stack/deploy.rs index 230c3e5eaf..b61806b535 100644 --- a/synthesizer/process/src/stack/deploy.rs +++ b/synthesizer/process/src/stack/deploy.rs @@ -124,12 +124,20 @@ impl Stack { lap!(timer, "Compute the request for {}", function.name()); // Initialize the assignments. let assignments = Assignments::::default(); + // Initialize the constraint limit. Account for the constraint added after synthesis that randomizes vars. + let constraint_limit = match verifying_key.circuit_info.num_constraints.checked_sub(1) { + // Since a deployment must always pay non-zero fee, it must always have at least one constraint. + None => { + bail!("The constraint limit of 0 for function '{}' is invalid", function.name()); + } + Some(limit) => limit, + }; // Initialize the call stack. let call_stack = CallStack::CheckDeployment( vec![request], burner_private_key, assignments.clone(), - Some(verifying_key.circuit_info.num_constraints as u64), + Some(constraint_limit as u64), ); // Append the function name, callstack, and assignments. call_stacks.push((function.name(), call_stack, assignments)); From 32053f959b94610dc6b197609cbf927c9864ae57 Mon Sep 17 00:00:00 2001 From: evan-schott <53463459+evan-schott@users.noreply.github.com> Date: Thu, 1 Feb 2024 15:52:20 -0500 Subject: [PATCH 27/35] Add back synthesis check to catch overreports --- synthesizer/process/src/stack/execute.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/synthesizer/process/src/stack/execute.rs b/synthesizer/process/src/stack/execute.rs index 11954e54f0..b0c6a37a0d 100644 --- a/synthesizer/process/src/stack/execute.rs +++ b/synthesizer/process/src/stack/execute.rs @@ -421,7 +421,15 @@ impl StackExecute for Stack { 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, constraint_limit) = registers.call_stack() { + // Ensure the assignment matches the constraint limit. + if let Some(constraint_limit) = constraint_limit { + ensure!( + assignment.num_constraints() == constraint_limit, + "The synthesized number of constraints ({}) does not match the declared limit in the verifying key ({constraint_limit})", + assignment.num_constraints(), + ); + } // Construct the call metrics. let metrics = CallMetrics { program_id: *self.program_id(), From ff10297663a2da1f6cce24812982acb97a49e824 Mon Sep 17 00:00:00 2001 From: evan-schott <53463459+evan-schott@users.noreply.github.com> Date: Thu, 1 Feb 2024 15:53:04 -0500 Subject: [PATCH 28/35] tx id changes bc deploy fee increased w/ this PR --- synthesizer/src/vm/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index 622420beeb..aaf5093874 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -951,7 +951,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_4.id(), deployment_1.id(), deployment_2.id(), deployment_3.id()], + vec![deployment_4.id(), deployment_1.id(), deployment_3.id(), deployment_2.id()], "Update me if serialization has changed" ); } From 32d3ca457529ccbc4b7605ff9f026d66326965bf Mon Sep 17 00:00:00 2001 From: evan-schott <53463459+evan-schott@users.noreply.github.com> Date: Thu, 1 Feb 2024 15:53:42 -0500 Subject: [PATCH 29/35] revise new tests --- synthesizer/src/vm/mod.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index aaf5093874..d9d28048c8 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -1175,10 +1175,13 @@ function do: Deployment::new(deployment.edition(), deployment.program().clone(), vks_with_overreport).unwrap(); let adjusted_transaction = Transaction::from_deployment(program_owner, adjusted_deployment, fee).unwrap(); - assert!(vm.check_transaction(&adjusted_transaction, None, rng).is_err()); + // Verify the deployment transaction. It should error during synthesis for constraint count mismatch. + let res = vm.check_transaction(&adjusted_transaction, None, rng); + assert!(res.is_err()); } #[test] + #[should_panic] fn test_deployment_synthesis_underreport() { let rng = &mut TestRng::default(); @@ -1227,8 +1230,8 @@ function do: 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 fail because the num_constraints in the vk are not correct. - assert!(vm.check_transaction(&adjusted_transaction, None, rng).is_err()); + // Verify the deployment transaction. It should panic when enforcing the first constraint over the vk limit. + vm.check_transaction(&adjusted_transaction, None, rng); } #[test] From 7d443bc45246bcf8e93a9d60ef42ce441d32c8ea Mon Sep 17 00:00:00 2001 From: evan-schott <53463459+evan-schott@users.noreply.github.com> Date: Thu, 1 Feb 2024 16:03:10 -0500 Subject: [PATCH 30/35] clippy --- synthesizer/src/vm/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index d9d28048c8..ce0e1db1af 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -1231,7 +1231,7 @@ function do: let adjusted_transaction = Transaction::Deploy(txid, program_owner, Box::new(adjusted_deployment), fee); // Verify the deployment transaction. It should panic when enforcing the first constraint over the vk limit. - vm.check_transaction(&adjusted_transaction, None, rng); + let _ = vm.check_transaction(&adjusted_transaction, None, rng); } #[test] From 763d5fea5961854fd9a0122845bb62aef5d5cc63 Mon Sep 17 00:00:00 2001 From: evan-schott <53463459+evan-schott@users.noreply.github.com> Date: Fri, 2 Feb 2024 13:24:20 -0500 Subject: [PATCH 31/35] fixes --- circuit/environment/src/circuit.rs | 2 +- synthesizer/process/src/stack/deploy.rs | 2 +- synthesizer/process/src/stack/execute.rs | 10 +--------- synthesizer/src/vm/mod.rs | 2 +- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/circuit/environment/src/circuit.rs b/circuit/environment/src/circuit.rs index 8e0788d01e..1fde647271 100644 --- a/circuit/environment/src/circuit.rs +++ b/circuit/environment/src/circuit.rs @@ -150,7 +150,7 @@ impl Environment for Circuit { // Ensure that we do not surpass the constraint limit for the circuit. CONSTRAINT_LIMIT.with(|constraint_limit| { if let Some(limit) = constraint_limit.get() { - if circuit.borrow().num_constraints() >= limit { + if circuit.borrow().num_constraints() > limit { Self::halt(format!("Surpassed the constraint limit ({limit})")) } } diff --git a/synthesizer/process/src/stack/deploy.rs b/synthesizer/process/src/stack/deploy.rs index b61806b535..36c6a5c708 100644 --- a/synthesizer/process/src/stack/deploy.rs +++ b/synthesizer/process/src/stack/deploy.rs @@ -124,7 +124,7 @@ impl Stack { lap!(timer, "Compute the request for {}", function.name()); // Initialize the assignments. let assignments = Assignments::::default(); - // Initialize the constraint limit. Account for the constraint added after synthesis that randomizes vars. + // Initialize the constraint limit. Account for the constraint added after synthesis that makes the Varuna zerocheck hiding. let constraint_limit = match verifying_key.circuit_info.num_constraints.checked_sub(1) { // Since a deployment must always pay non-zero fee, it must always have at least one constraint. None => { diff --git a/synthesizer/process/src/stack/execute.rs b/synthesizer/process/src/stack/execute.rs index b0c6a37a0d..11954e54f0 100644 --- a/synthesizer/process/src/stack/execute.rs +++ b/synthesizer/process/src/stack/execute.rs @@ -421,15 +421,7 @@ impl StackExecute for Stack { lap!(timer, "Save the transition"); } // If the circuit is in `CheckDeployment` mode, then save the assignment. - else if let CallStack::CheckDeployment(_, _, ref assignments, constraint_limit) = registers.call_stack() { - // Ensure the assignment matches the constraint limit. - if let Some(constraint_limit) = constraint_limit { - ensure!( - assignment.num_constraints() == constraint_limit, - "The synthesized number of constraints ({}) does not match the declared limit in the verifying key ({constraint_limit})", - assignment.num_constraints(), - ); - } + 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/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index ce0e1db1af..7e0756977a 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -1175,7 +1175,7 @@ function do: Deployment::new(deployment.edition(), deployment.program().clone(), vks_with_overreport).unwrap(); let adjusted_transaction = Transaction::from_deployment(program_owner, adjusted_deployment, fee).unwrap(); - // Verify the deployment transaction. It should error during synthesis for constraint count mismatch. + // Verify the deployment transaction. It should error when certificate checking for constraint count mismatch. let res = vm.check_transaction(&adjusted_transaction, None, rng); assert!(res.is_err()); } From 6adc557813eb87df59a9c0890cfd5cff549ab791 Mon Sep 17 00:00:00 2001 From: evan-schott <53463459+evan-schott@users.noreply.github.com> Date: Fri, 2 Feb 2024 14:00:32 -0500 Subject: [PATCH 32/35] fixes --- synthesizer/src/vm/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index 7e0756977a..2f411b83f6 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -1220,7 +1220,7 @@ function do: let mut vks_with_underreport = Vec::with_capacity(deployment.verifying_keys().len()); for (id, (vk, cert)) in deployment.verifying_keys() { let mut vk = vk.deref().clone(); - vk.circuit_info.num_constraints -= 1; + vk.circuit_info.num_constraints -= 2; let vk = VerifyingKey::new(Arc::new(vk)); vks_with_underreport.push((*id, (vk, cert.clone()))); } From b746a196ed5d54c6f3616b4191419406c2667013 Mon Sep 17 00:00:00 2001 From: Victor Sint Nicolaas Date: Thu, 8 Feb 2024 08:17:12 +0100 Subject: [PATCH 33/35] Correct underreport test program name --- synthesizer/src/vm/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index 2f411b83f6..977277f676 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -1199,7 +1199,7 @@ function do: // Deploy the base program. let program = Program::from_str( r" -program synthesis_overreport.aleo; +program synthesis_underreport.aleo; function do: input r0 as u32.private; From 58e6c391863b731d9110e2bcedcddaf77b1bb1e1 Mon Sep 17 00:00:00 2001 From: Victor Sint Nicolaas Date: Thu, 8 Feb 2024 09:52:39 +0100 Subject: [PATCH 34/35] Correct test expectations --- synthesizer/src/vm/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index e3052d9434..a24d2c89c0 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -951,7 +951,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_4.id(), deployment_1.id(), deployment_3.id(), deployment_2.id()], + vec![deployment_2.id(), deployment_1.id(), deployment_4.id(), deployment_3.id()], "Update me if serialization has changed" ); } From 411218bff9d51ac1db4badfe5470203d0178320f Mon Sep 17 00:00:00 2001 From: Howard Wu <9260812+howardwu@users.noreply.github.com> Date: Thu, 8 Feb 2024 18:14:18 -0800 Subject: [PATCH 35/35] nit: use let-else syntax --- synthesizer/process/src/stack/deploy.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/synthesizer/process/src/stack/deploy.rs b/synthesizer/process/src/stack/deploy.rs index 534c8a2142..be662d134f 100644 --- a/synthesizer/process/src/stack/deploy.rs +++ b/synthesizer/process/src/stack/deploy.rs @@ -133,12 +133,9 @@ impl Stack { // Initialize the assignments. let assignments = Assignments::::default(); // Initialize the constraint limit. Account for the constraint added after synthesis that makes the Varuna zerocheck hiding. - let constraint_limit = match verifying_key.circuit_info.num_constraints.checked_sub(1) { + let Some(constraint_limit) = verifying_key.circuit_info.num_constraints.checked_sub(1) else { // Since a deployment must always pay non-zero fee, it must always have at least one constraint. - None => { - bail!("The constraint limit of 0 for function '{}' is invalid", function.name()); - } - Some(limit) => limit, + bail!("The constraint limit of 0 for function '{}' is invalid", function.name()); }; // Initialize the call stack. let call_stack = CallStack::CheckDeployment(