verify_fee.rs
1 // Copyright (c) 2025 ADnet Contributors 2 // This file is part of the AlphaVM library. 3 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at: 7 8 // http://www.apache.org/licenses/LICENSE-2.0 9 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 use super::*; 17 18 impl<N: Network> Process<N> { 19 /// Verifies the given fee is valid. 20 /// Note: This does *not* check that the global state root exists in the ledger. 21 #[inline] 22 pub fn verify_fee( 23 &self, 24 consensus_version: ConsensusVersion, 25 varuna_version: VarunaVersion, 26 inclusion_version: InclusionVersion, 27 fee: &Fee<N>, 28 deployment_or_execution_id: Field<N>, 29 ) -> Result<()> { 30 let timer = timer!("Process::verify_fee"); 31 32 // Retrieve the stack. 33 let stack = self.get_stack(fee.program_id())?; 34 // Retrieve the function from the stack. 35 let function = stack.get_function(fee.function_name())?; 36 37 dev_println!("Verifying fee from {}/{}...", fee.program_id(), fee.function_name()); 38 39 #[cfg(debug_assertions)] 40 { 41 // Ensure the number of function calls in this function is 1. 42 if stack.get_number_of_calls(function.name())? != 1 { 43 bail!("The number of function calls in '{}/{}' should be 1", stack.program_id(), function.name()) 44 } 45 // Debug-mode only, as the `Transition` constructor recomputes the transition ID at initialization. 46 debug_assert_eq!( 47 **fee.id(), 48 N::hash_bhp512(&(fee.to_root()?, *fee.tcm()).to_bits_le())?, 49 "Transition ID of the fee is incorrect" 50 ); 51 } 52 53 // Determine if the fee is private. 54 let is_fee_private = fee.is_fee_private(); 55 // Determine if the fee is public. 56 let is_fee_public = fee.is_fee_public(); 57 // Ensure the fee has the correct program ID and function. 58 ensure!(is_fee_private || is_fee_public, "Incorrect program ID or function name for fee transition"); 59 // Ensure the number of inputs is within the allowed range. 60 ensure!(fee.inputs().len() <= N::MAX_INPUTS, "Fee exceeded maximum number of inputs"); 61 // Ensure the number of outputs is within the allowed range. 62 ensure!(fee.outputs().len() <= N::MAX_INPUTS, "Fee exceeded maximum number of outputs"); 63 64 // Ensure the input and output types are equivalent to the ones defined in the function. 65 // We only need to check that the variant type matches because we already check the hashes in 66 // the `Input::verify` and `Output::verify` functions. 67 let fee_input_variants = fee.inputs().iter().map(Input::variant).collect::<Vec<_>>(); 68 let fee_output_variants = fee.outputs().iter().map(Output::variant).collect::<Vec<_>>(); 69 ensure!(function.input_variants() == fee_input_variants, "The fee input variants do not match"); 70 ensure!(function.output_variants() == fee_output_variants, "The fee output variants do not match"); 71 72 // Retrieve the candidate deployment or execution ID. 73 let Ok(candidate_id) = fee.deployment_or_execution_id() else { 74 bail!("Failed to get the deployment or execution ID in the fee transition") 75 }; 76 // Ensure the candidate ID is the deployment or execution ID. 77 if candidate_id != deployment_or_execution_id { 78 bail!("Incorrect deployment or execution ID in the fee transition") 79 } 80 lap!(timer, "Verify the deployment or execution ID"); 81 82 // Verify the fee transition is well-formed. 83 match is_fee_private { 84 true => self.verify_fee_private(consensus_version, varuna_version, inclusion_version, &fee)?, 85 false => self.verify_fee_public(varuna_version, inclusion_version, &fee)?, 86 } 87 finish!(timer, "Verify the fee transition"); 88 Ok(()) 89 } 90 } 91 92 impl<N: Network> Process<N> { 93 /// Verifies the transition for `credits.alpha/fee_private` is well-formed. 94 fn verify_fee_private( 95 &self, 96 consensus_version: ConsensusVersion, 97 varuna_version: VarunaVersion, 98 inclusion_version: InclusionVersion, 99 fee: &&Fee<N>, 100 ) -> Result<()> { 101 let timer = timer!("Process::verify_fee_private"); 102 103 // Retrieve the network ID. 104 let network_id = U16::new(N::ID); 105 // Compute the function ID. 106 let function_id = compute_function_id(&network_id, fee.program_id(), fee.function_name())?; 107 108 // Ensure the fee contains 1 input record. 109 ensure!( 110 fee.inputs().iter().filter(|input| matches!(input, Input::Record(..))).count() == 1, 111 "The fee transition must contain *1* input record" 112 ); 113 // Ensure the number of inputs is correct. 114 let num_inputs = fee.inputs().len(); 115 ensure!(num_inputs == 4, "The number of inputs in the fee transition should be 4, found {num_inputs}",); 116 // Ensure each input is valid. 117 if fee.inputs().iter().enumerate().any(|(index, input)| !input.verify(function_id, fee.tcm(), index)) { 118 bail!("Failed to verify a fee input") 119 } 120 lap!(timer, "Verify the inputs"); 121 122 // Ensure the number of outputs is correct. 123 ensure!( 124 fee.outputs().len() == 1, 125 "The number of outputs in the fee transition should be 1, found {}", 126 fee.outputs().len() 127 ); 128 // Ensure each output is valid. 129 for (index, output) in fee.outputs().iter().enumerate() { 130 // If the consensus version are before `ConsensusVersion::V8`, ensure the output record is on Version 0. 131 // if the consensus version is on or after `ConsensusVersion::V8`, ensure the output record is on Version 1. 132 if let Some((_, record)) = output.record() { 133 if (ConsensusVersion::V1..=ConsensusVersion::V7).contains(&consensus_version) { 134 #[cfg(not(any(test, feature = "test")))] 135 ensure!(record.version().is_zero(), "Output record must be Version 0 before Consensus V8"); 136 #[cfg(any(test, feature = "test"))] 137 ensure!( 138 record.version().is_one(), 139 "Output record must be Version 1 before Consensus V8 in tests." 140 ); 141 } else { 142 ensure!(record.version().is_one(), "Output record must be Version 1 on or after Consensus V8"); 143 } 144 } 145 // Ensure the output is valid. 146 if !output.verify(function_id, fee.tcm(), num_inputs + index) { 147 bail!("Failed to verify a fee output") 148 } 149 } 150 lap!(timer, "Verify the outputs"); 151 152 // Compute the x- and y-coordinate of `tpk`. 153 let (tpk_x, tpk_y) = fee.tpk().to_xy_coordinates(); 154 155 // Retrieve the address belonging to the program ID. 156 let stack = self.get_stack(fee.program_id())?; 157 let program_address = stack.program_address(); 158 159 // Compute the x- and y-coordinate of `parent`. 160 let (parent_x, parent_y) = program_address.to_xy_coordinates(); 161 162 // Construct the public inputs to verify the proof. 163 let mut inputs = vec![N::Field::one(), *tpk_x, *tpk_y, **fee.tcm(), **fee.scm()]; 164 // Extend the inputs with the input IDs. 165 inputs.extend(fee.inputs().iter().flat_map(|input| input.verifier_inputs())); 166 // Extend the verifier inputs with the public inputs for 'self.caller'. 167 inputs.extend([*Field::<N>::one(), *parent_x, *parent_y]); 168 // Extend the inputs with the output IDs. 169 inputs.extend(fee.outputs().iter().flat_map(|output| output.verifier_inputs())); 170 lap!(timer, "Construct the verifier inputs"); 171 172 dev_println!("Fee public inputs ({} elements): {:#?}", inputs.len(), inputs); 173 174 // Retrieve the verifying key. 175 let verifying_key = stack.get_verifying_key(fee.function_name())?; 176 177 // Ensure the fee proof is valid. 178 Trace::verify_fee_proof(varuna_version, inclusion_version, (verifying_key, vec![inputs]), fee)?; 179 finish!(timer, "Verify the fee proof"); 180 Ok(()) 181 } 182 183 /// Verifies the transition for `credits.alpha/fee_public` is well-formed. 184 /// Attention: This method does *not* verify the account balance is sufficient. 185 fn verify_fee_public( 186 &self, 187 varuna_version: VarunaVersion, 188 inclusion_version: InclusionVersion, 189 fee: &&Fee<N>, 190 ) -> Result<()> { 191 let timer = timer!("Process::verify_fee_public"); 192 193 // Retrieve the network ID. 194 let network_id = U16::new(N::ID); 195 // Compute the function ID. 196 let function_id = compute_function_id(&network_id, fee.program_id(), fee.function_name())?; 197 198 // Ensure the fee contains all public inputs. 199 ensure!( 200 fee.inputs().iter().all(|input| matches!(input, Input::Public(..))), 201 "The fee transition must contain *only* public inputs" 202 ); 203 // Ensure the number of inputs is correct. 204 let num_inputs = fee.inputs().len(); 205 ensure!(num_inputs == 3, "The number of inputs in the fee transition should be 3, found {num_inputs}",); 206 // Ensure each input is valid. 207 if fee.inputs().iter().enumerate().any(|(index, input)| !input.verify(function_id, fee.tcm(), index)) { 208 bail!("Failed to verify a fee input") 209 } 210 lap!(timer, "Verify the inputs"); 211 212 // Ensure there is one output. 213 ensure!( 214 fee.outputs().len() == 1, 215 "The number of outputs in the fee transition should be 1, found {}", 216 fee.outputs().len() 217 ); 218 // Ensure each output is valid. 219 if fee 220 .outputs() 221 .iter() 222 .enumerate() 223 .any(|(index, output)| !output.verify(function_id, fee.tcm(), num_inputs + index)) 224 { 225 bail!("Failed to verify a fee output") 226 } 227 lap!(timer, "Verify the outputs"); 228 229 // Compute the x- and y-coordinate of `tpk`. 230 let (tpk_x, tpk_y) = fee.tpk().to_xy_coordinates(); 231 232 // Retrieve the address belonging to the program ID. 233 let stack = self.get_stack(fee.program_id())?; 234 let program_address = stack.program_address(); 235 236 // Compute the x- and y-coordinate of `parent`. 237 let (parent_x, parent_y) = program_address.to_xy_coordinates(); 238 239 // Construct the public inputs to verify the proof. 240 let mut inputs = vec![N::Field::one(), *tpk_x, *tpk_y, **fee.tcm(), **fee.scm()]; 241 // Extend the inputs with the input IDs. 242 inputs.extend(fee.inputs().iter().flat_map(|input| input.verifier_inputs())); 243 // Extend the verifier inputs with the public inputs for 'self.caller' 244 inputs.extend([*Field::<N>::one(), *parent_x, *parent_y]); 245 // Extend the inputs with the output IDs. 246 inputs.extend(fee.outputs().iter().flat_map(|output| output.verifier_inputs())); 247 lap!(timer, "Construct the verifier inputs"); 248 249 dev_println!("Fee public inputs ({} elements): {:#?}", inputs.len(), inputs); 250 251 // Retrieve the verifying key. 252 let verifying_key = stack.get_verifying_key(fee.function_name())?; 253 254 // Ensure the fee proof is valid. 255 Trace::verify_fee_proof(varuna_version, inclusion_version, (verifying_key, vec![inputs]), fee)?; 256 finish!(timer, "Verify the fee proof"); 257 Ok(()) 258 } 259 } 260 261 #[cfg(test)] 262 mod tests { 263 use super::*; 264 use alphavm_ledger_block::Transaction; 265 use console::prelude::TestRng; 266 267 #[test] 268 fn test_verify_fee() { 269 let rng = &mut TestRng::default(); 270 271 // Fetch transactions. 272 let transactions = [ 273 alphavm_ledger_test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), true, rng), 274 alphavm_ledger_test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), false, rng), 275 alphavm_ledger_test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), true, rng), 276 alphavm_ledger_test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), false, rng), 277 alphavm_ledger_test_helpers::sample_execution_transaction_with_fee(true, rng, 0), 278 alphavm_ledger_test_helpers::sample_execution_transaction_with_fee(false, rng, 0), 279 alphavm_ledger_test_helpers::sample_fee_private_transaction(rng), 280 alphavm_ledger_test_helpers::sample_fee_public_transaction(rng), 281 ]; 282 283 // Construct a new process. 284 let process = Process::load().unwrap(); 285 286 for transaction in transactions { 287 match transaction { 288 Transaction::Deploy(_, _, _, deployment, fee) => { 289 // Compute the deployment ID. 290 let deployment_id = deployment.to_deployment_id().unwrap(); 291 // Verify the fee. 292 process 293 .verify_fee(ConsensusVersion::V8, VarunaVersion::V1, InclusionVersion::V0, &fee, deployment_id) 294 .unwrap(); 295 } 296 Transaction::Execute(_, _, execution, fee) => { 297 // Compute the execution ID. 298 let execution_id = execution.to_execution_id().unwrap(); 299 // Verify the fee. 300 process 301 .verify_fee( 302 ConsensusVersion::V8, 303 VarunaVersion::V1, 304 InclusionVersion::V0, 305 &fee.unwrap(), 306 execution_id, 307 ) 308 .unwrap(); 309 } 310 Transaction::Fee(_, fee) => match fee.is_fee_private() { 311 true => process 312 .verify_fee_private(ConsensusVersion::V8, VarunaVersion::V1, InclusionVersion::V0, &&fee) 313 .unwrap(), 314 false => process.verify_fee_public(VarunaVersion::V1, InclusionVersion::V0, &&fee).unwrap(), 315 }, 316 } 317 } 318 } 319 }