snarkvm_synthesizer_debug/vm/
verify.rs

1// Copyright (C) 2019-2023 Aleo Systems Inc.
2// This file is part of the snarkVM 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// http://www.apache.org/licenses/LICENSE-2.0
8
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use super::*;
16
17/// Ensures the given iterator has no duplicate elements, and that the ledger
18/// does not already contain a given item.
19macro_rules! ensure_is_unique {
20    ($name:expr, $self:expr, $method:ident, $iter:expr) => {
21        // Ensure there are no duplicate items in the transaction.
22        if has_duplicates($iter) {
23            bail!("Found a duplicate {} in the transaction", $name);
24        }
25        // Ensure the ledger does not already contain a given item.
26        for item in $iter {
27            if $self.transition_store().$method(item)? {
28                bail!("The {} '{}' already exists in the ledger", $name, item)
29            }
30        }
31    };
32}
33
34impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
35    /// Verifies the transaction in the VM. On failure, returns an error.
36    #[inline]
37    pub fn check_transaction<R: CryptoRng + Rng>(
38        &self,
39        transaction: &Transaction<N>,
40        rejected_id: Option<Field<N>>,
41        rng: &mut R,
42    ) -> Result<()> {
43        let timer = timer!("VM::check_transaction");
44
45        /* Transaction */
46
47        // Ensure the transaction ID is unique.
48        if self.block_store().contains_transaction_id(&transaction.id())? {
49            bail!("Transaction '{}' already exists in the ledger", transaction.id())
50        }
51
52        // Compute the Merkle root of the transaction.
53        match transaction.to_root() {
54            // Ensure the transaction ID is correct.
55            Ok(root) if *transaction.id() != root => bail!("Incorrect transaction ID ({})", transaction.id()),
56            Ok(_) => (),
57            Err(error) => {
58                bail!("Failed to compute the Merkle root of the transaction: {error}\n{transaction}");
59            }
60        };
61        lap!(timer, "Verify the transaction ID");
62
63        /* Transition */
64
65        // Ensure the transition IDs are unique.
66        ensure_is_unique!("transition ID", self, contains_transition_id, transaction.transition_ids());
67
68        /* Input */
69
70        // Ensure the input IDs are unique.
71        ensure_is_unique!("input ID", self, contains_input_id, transaction.input_ids());
72        // Ensure the serial numbers are unique.
73        ensure_is_unique!("serial number", self, contains_serial_number, transaction.serial_numbers());
74        // Ensure the tags are unique.
75        ensure_is_unique!("tag", self, contains_tag, transaction.tags());
76
77        /* Output */
78
79        // Ensure the output IDs are unique.
80        ensure_is_unique!("output ID", self, contains_output_id, transaction.output_ids());
81        // Ensure the commitments are unique.
82        ensure_is_unique!("commitment", self, contains_commitment, transaction.commitments());
83        // Ensure the nonces are unique.
84        ensure_is_unique!("nonce", self, contains_nonce, transaction.nonces());
85
86        /* Metadata */
87
88        // Ensure the transition public keys are unique.
89        ensure_is_unique!("transition public key", self, contains_tpk, transaction.transition_public_keys());
90        // Ensure the transition commitments are unique.
91        ensure_is_unique!("transition commitment", self, contains_tcm, transaction.transition_commitments());
92
93        lap!(timer, "Check for duplicate elements");
94
95        // First, verify the fee.
96        self.check_fee(transaction, rejected_id)?;
97
98        // Next, verify the deployment or execution.
99        match transaction {
100            Transaction::Deploy(id, owner, deployment, _) => {
101                // Compute the deployment ID.
102                let Ok(deployment_id) = deployment.to_deployment_id() else {
103                    bail!("Failed to compute the Merkle root for a deployment transaction '{id}'")
104                };
105                // Verify the signature corresponds to the transaction ID.
106                ensure!(owner.verify(deployment_id), "Invalid owner signature for deployment transaction '{id}'");
107                // Ensure the edition is correct.
108                if deployment.edition() != N::EDITION {
109                    bail!("Invalid deployment transaction '{id}' - expected edition {}", N::EDITION)
110                }
111                // Ensure the program ID does not already exist..
112                if self.transaction_store().contains_program_id(deployment.program_id())? {
113                    bail!("Program ID '{}' is already deployed", deployment.program_id())
114                }
115                // Verify the deployment.
116                self.check_deployment_internal(deployment, rng)?;
117            }
118            Transaction::Execute(id, execution, _) => {
119                // Compute the execution ID.
120                let Ok(execution_id) = execution.to_execution_id() else {
121                    bail!("Failed to compute the Merkle root for an execution transaction '{id}'")
122                };
123                // Ensure the execution was not previously rejected (replay attack prevention).
124                if self.block_store().contains_rejected_deployment_or_execution_id(&execution_id)? {
125                    bail!("Transaction '{id}' contains a previously rejected execution")
126                }
127                // Verify the execution.
128                self.check_execution_internal(execution)?;
129            }
130            Transaction::Fee(..) => { /* no-op */ }
131        }
132
133        finish!(timer, "Verify the transaction");
134        Ok(())
135    }
136
137    /// Verifies the `fee` in the given transaction. On failure, returns an error.
138    #[inline]
139    pub fn check_fee(&self, transaction: &Transaction<N>, rejected_id: Option<Field<N>>) -> Result<()> {
140        match transaction {
141            Transaction::Deploy(id, _, deployment, fee) => {
142                // Ensure the rejected ID is not present.
143                ensure!(rejected_id.is_none(), "Transaction '{id}' should not have a rejected ID (deployment)");
144                // Compute the deployment ID.
145                let Ok(deployment_id) = deployment.to_deployment_id() else {
146                    bail!("Failed to compute the Merkle root for deployment transaction '{id}'")
147                };
148                // Compute the deployment cost.
149                let (cost, _) = deployment_cost(deployment)?;
150                // Ensure the fee is sufficient to cover the cost.
151                if *fee.base_amount()? < cost {
152                    bail!("Transaction '{id}' has an insufficient base fee (deployment) - requires {cost} microcredits")
153                }
154                // Verify the fee.
155                self.check_fee_internal(fee, deployment_id)?;
156            }
157            Transaction::Execute(id, execution, fee) => {
158                // Ensure the rejected ID is not present.
159                ensure!(rejected_id.is_none(), "Transaction '{id}' should not have a rejected ID (execution)");
160                // Compute the execution ID.
161                let Ok(execution_id) = execution.to_execution_id() else {
162                    bail!("Failed to compute the Merkle root for execution transaction '{id}'")
163                };
164                // If the transaction contains only 1 transition, and the transition is a split, then the fee can be skipped.
165                let is_fee_required = !(execution.len() == 1 && transaction.contains_split());
166                // Verify the fee.
167                if let Some(fee) = fee {
168                    // If the fee is required, then check that the base fee amount is satisfied.
169                    if is_fee_required {
170                        // Compute the execution cost.
171                        let (cost, _) = execution_cost(self, execution)?;
172                        // Ensure the fee is sufficient to cover the cost.
173                        if *fee.base_amount()? < cost {
174                            bail!(
175                                "Transaction '{id}' has an insufficient base fee (execution) - requires {cost} microcredits"
176                            )
177                        }
178                    } else {
179                        // Ensure the base fee amount is zero.
180                        ensure!(*fee.base_amount()? == 0, "Transaction '{id}' has a non-zero base fee (execution)");
181                    }
182                    // Verify the fee.
183                    self.check_fee_internal(fee, execution_id)?;
184                } else {
185                    // Ensure the fee can be safely skipped.
186                    ensure!(!is_fee_required, "Transaction '{id}' is missing a fee (execution)");
187                }
188            }
189            // Note: This transaction type does not need to check the fee amount, because:
190            //  1. The fee is guaranteed to be non-zero by the constructor of `Transaction::Fee`.
191            //  2. The fee may be less that the deployment or execution cost, as this is a valid reason it was rejected.
192            Transaction::Fee(id, fee) => {
193                // Verify the fee.
194                match rejected_id {
195                    Some(rejected_id) => self.check_fee_internal(fee, rejected_id)?,
196                    None => bail!("Transaction '{id}' is missing a rejected ID (fee)"),
197                }
198            }
199        }
200        Ok(())
201    }
202}
203
204impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
205    /// Verifies the given deployment. On failure, returns an error.
206    ///
207    /// Note: This is an internal check only. To ensure all components of the deployment are checked,
208    /// use `VM::check_transaction` instead.
209    #[inline]
210    fn check_deployment_internal<R: CryptoRng + Rng>(&self, deployment: &Deployment<N>, rng: &mut R) -> Result<()> {
211        macro_rules! logic {
212            ($process:expr, $network:path, $aleo:path) => {{
213                // Prepare the deployment.
214                let deployment = cast_ref!(&deployment as Deployment<$network>);
215                // Verify the deployment.
216                $process.verify_deployment::<$aleo, _>(&deployment, rng)
217            }};
218        }
219
220        // Process the logic.
221        let timer = timer!("VM::check_deployment");
222        let result = process!(self, logic).map_err(|error| anyhow!("Deployment verification failed - {error}"));
223        finish!(timer);
224        result
225    }
226
227    /// Verifies the given execution. On failure, returns an error.
228    ///
229    /// Note: This is an internal check only. To ensure all components of the execution are checked,
230    /// use `VM::check_transaction` instead.
231    #[inline]
232    fn check_execution_internal(&self, execution: &Execution<N>) -> Result<()> {
233        let timer = timer!("VM::check_execution");
234
235        // Verify the execution.
236        let verification = self.process.read().verify_execution(execution);
237        lap!(timer, "Verify the execution");
238
239        // Ensure the global state root exists in the block store.
240        let result = match verification {
241            // Ensure the global state root exists in the block store.
242            Ok(()) => match self.block_store().contains_state_root(&execution.global_state_root()) {
243                Ok(true) => Ok(()),
244                Ok(false) => bail!("Execution verification failed: global state root not found"),
245                Err(error) => bail!("Execution verification failed: {error}"),
246            },
247            Err(error) => bail!("Execution verification failed: {error}"),
248        };
249        finish!(timer, "Check the global state root");
250        result
251    }
252
253    /// Verifies the given fee. On failure, returns an error.
254    ///
255    /// Note: This is an internal check only. To ensure all components of the fee are checked,
256    /// use `VM::check_fee` instead.
257    #[inline]
258    fn check_fee_internal(&self, fee: &Fee<N>, deployment_or_execution_id: Field<N>) -> Result<()> {
259        let timer = timer!("VM::check_fee");
260
261        // Ensure the fee does not exceed the limit.
262        let fee_amount = fee.amount()?;
263        ensure!(*fee_amount <= N::MAX_FEE, "Fee verification failed: fee exceeds the maximum limit");
264
265        // Verify the fee.
266        let verification = self.process.read().verify_fee(fee, deployment_or_execution_id);
267        lap!(timer, "Verify the fee");
268
269        // TODO (howardwu): This check is technically insufficient. Consider moving this upstream
270        //  to the speculation layer.
271        // If the fee is public, speculatively check the account balance.
272        if fee.is_fee_public() {
273            // Retrieve the payer.
274            let Some(payer) = fee.payer() else {
275                bail!("Fee verification failed: fee is public, but the payer is missing");
276            };
277            // Retrieve the account balance of the payer.
278            let Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) =
279                self.finalize_store().get_value_speculative(
280                    ProgramID::from_str("credits.aleo")?,
281                    Identifier::from_str("account")?,
282                    &Plaintext::from(Literal::Address(payer)),
283                )?
284            else {
285                bail!("Fee verification failed: fee is public, but the payer account balance is missing");
286            };
287            // Ensure the balance is sufficient.
288            ensure!(balance >= fee_amount, "Fee verification failed: insufficient balance");
289        }
290
291        // Ensure the global state root exists in the block store.
292        let result = match verification {
293            Ok(()) => match self.block_store().contains_state_root(&fee.global_state_root()) {
294                Ok(true) => Ok(()),
295                Ok(false) => bail!("Fee verification failed: global state root not found"),
296                Err(error) => bail!("Fee verification failed: {error}"),
297            },
298            Err(error) => bail!("Fee verification failed: {error}"),
299        };
300        finish!(timer, "Check the global state root");
301        result
302    }
303}
304
305#[cfg(test)]
306mod tests {
307    use super::*;
308
309    use crate::vm::test_helpers::sample_finalize_state;
310    use console::{
311        account::{Address, ViewKey},
312        types::Field,
313    };
314    use ledger_block::{Block, Header, Metadata, Transaction};
315
316    type CurrentNetwork = test_helpers::CurrentNetwork;
317
318    #[test]
319    fn test_verify() {
320        let rng = &mut TestRng::default();
321        let vm = crate::vm::test_helpers::sample_vm_with_genesis_block(rng);
322
323        // Fetch a deployment transaction.
324        let deployment_transaction = crate::vm::test_helpers::sample_deployment_transaction(rng);
325        // Ensure the transaction verifies.
326        vm.check_transaction(&deployment_transaction, None, rng).unwrap();
327
328        // Fetch an execution transaction.
329        let execution_transaction = crate::vm::test_helpers::sample_execution_transaction_with_private_fee(rng);
330        // Ensure the transaction verifies.
331        vm.check_transaction(&execution_transaction, None, rng).unwrap();
332
333        // Fetch an execution transaction.
334        let execution_transaction = crate::vm::test_helpers::sample_execution_transaction_with_public_fee(rng);
335        // Ensure the transaction verifies.
336        vm.check_transaction(&execution_transaction, None, rng).unwrap();
337    }
338
339    #[test]
340    fn test_verify_deployment() {
341        let rng = &mut TestRng::default();
342        let vm = crate::vm::test_helpers::sample_vm();
343
344        // Fetch the program from the deployment.
345        let program = crate::vm::test_helpers::sample_program();
346
347        // Deploy the program.
348        let deployment = vm.deploy_raw(&program, rng).unwrap();
349
350        // Ensure the deployment is valid.
351        vm.check_deployment_internal(&deployment, rng).unwrap();
352
353        // Ensure that deserialization doesn't break the transaction verification.
354        let serialized_deployment = deployment.to_string();
355        let deployment_transaction: Deployment<CurrentNetwork> = serde_json::from_str(&serialized_deployment).unwrap();
356        vm.check_deployment_internal(&deployment_transaction, rng).unwrap();
357    }
358
359    #[test]
360    fn test_verify_execution() {
361        let rng = &mut TestRng::default();
362        let vm = crate::vm::test_helpers::sample_vm_with_genesis_block(rng);
363
364        // Fetch execution transactions.
365        let transactions = [
366            crate::vm::test_helpers::sample_execution_transaction_with_private_fee(rng),
367            crate::vm::test_helpers::sample_execution_transaction_with_public_fee(rng),
368        ];
369
370        for transaction in transactions {
371            match transaction {
372                Transaction::Execute(_, execution, _) => {
373                    // Ensure the proof exists.
374                    assert!(execution.proof().is_some());
375                    // Verify the execution.
376                    vm.check_execution_internal(&execution).unwrap();
377
378                    // Ensure that deserialization doesn't break the transaction verification.
379                    let serialized_execution = execution.to_string();
380                    let recovered_execution: Execution<CurrentNetwork> =
381                        serde_json::from_str(&serialized_execution).unwrap();
382                    vm.check_execution_internal(&recovered_execution).unwrap();
383                }
384                _ => panic!("Expected an execution transaction"),
385            }
386        }
387    }
388
389    #[test]
390    fn test_verify_fee() {
391        let rng = &mut TestRng::default();
392        let vm = crate::vm::test_helpers::sample_vm_with_genesis_block(rng);
393
394        // Fetch execution transactions.
395        let transactions = [
396            crate::vm::test_helpers::sample_execution_transaction_with_private_fee(rng),
397            crate::vm::test_helpers::sample_execution_transaction_with_public_fee(rng),
398        ];
399
400        for transaction in transactions {
401            match transaction {
402                Transaction::Execute(_, execution, Some(fee)) => {
403                    let execution_id = execution.to_execution_id().unwrap();
404
405                    // Ensure the proof exists.
406                    assert!(fee.proof().is_some());
407                    // Verify the fee.
408                    vm.check_fee_internal(&fee, execution_id).unwrap();
409
410                    // Ensure that deserialization doesn't break the transaction verification.
411                    let serialized_fee = fee.to_string();
412                    let recovered_fee: Fee<CurrentNetwork> = serde_json::from_str(&serialized_fee).unwrap();
413                    vm.check_fee_internal(&recovered_fee, execution_id).unwrap();
414                }
415                _ => panic!("Expected an execution with a fee"),
416            }
417        }
418    }
419
420    #[test]
421    fn test_check_transaction_execution() {
422        let rng = &mut TestRng::default();
423
424        // Initialize the VM.
425        let vm = crate::vm::test_helpers::sample_vm();
426        // Initialize the genesis block.
427        let genesis = crate::vm::test_helpers::sample_genesis_block(rng);
428        // Update the VM.
429        vm.add_next_block(&genesis).unwrap();
430
431        // Fetch a valid execution transaction with a private fee.
432        let valid_transaction = crate::vm::test_helpers::sample_execution_transaction_with_private_fee(rng);
433        vm.check_transaction(&valid_transaction, None, rng).unwrap();
434
435        // Fetch a valid execution transaction with a public fee.
436        let valid_transaction = crate::vm::test_helpers::sample_execution_transaction_with_public_fee(rng);
437        vm.check_transaction(&valid_transaction, None, rng).unwrap();
438
439        // Fetch an valid execution transaction with no fee.
440        let valid_transaction = crate::vm::test_helpers::sample_execution_transaction_without_fee(rng);
441        vm.check_transaction(&valid_transaction, None, rng).unwrap();
442    }
443
444    #[test]
445    fn test_verify_deploy_and_execute() {
446        // Initialize the RNG.
447        let rng = &mut TestRng::default();
448
449        // Initialize a new caller.
450        let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng);
451        let caller_view_key = ViewKey::try_from(&caller_private_key).unwrap();
452        let address = Address::try_from(&caller_private_key).unwrap();
453
454        // Initialize the genesis block.
455        let genesis = crate::vm::test_helpers::sample_genesis_block(rng);
456
457        // Fetch the unspent records.
458        let records = genesis.records().collect::<indexmap::IndexMap<_, _>>();
459
460        // Prepare the fee.
461        let credits = records.values().next().unwrap().decrypt(&caller_view_key).unwrap();
462
463        // Initialize the VM.
464        let vm = crate::vm::test_helpers::sample_vm();
465        // Update the VM.
466        vm.add_next_block(&genesis).unwrap();
467
468        // Deploy.
469        let program = crate::vm::test_helpers::sample_program();
470        let deployment_transaction = vm.deploy(&caller_private_key, &program, Some(credits), 10, None, rng).unwrap();
471
472        // Construct the new block header.
473        let (ratifications, transactions, aborted_transaction_ids, ratified_finalize_operations) =
474            vm.speculate(sample_finalize_state(1), Some(0u64), vec![], None, [deployment_transaction].iter()).unwrap();
475        assert!(aborted_transaction_ids.is_empty());
476
477        // Construct the metadata associated with the block.
478        let deployment_metadata = Metadata::new(
479            CurrentNetwork::ID,
480            1,
481            1,
482            0,
483            0,
484            CurrentNetwork::GENESIS_COINBASE_TARGET,
485            CurrentNetwork::GENESIS_PROOF_TARGET,
486            genesis.last_coinbase_target(),
487            genesis.last_coinbase_timestamp(),
488            CurrentNetwork::GENESIS_TIMESTAMP + 1,
489        )
490        .unwrap();
491
492        let deployment_header = Header::from(
493            vm.block_store().current_state_root(),
494            transactions.to_transactions_root().unwrap(),
495            transactions.to_finalize_root(ratified_finalize_operations).unwrap(),
496            ratifications.to_ratifications_root().unwrap(),
497            Field::zero(),
498            Field::zero(),
499            deployment_metadata,
500        )
501        .unwrap();
502
503        // Construct a new block for the deploy transaction.
504        let deployment_block = Block::new_beacon(
505            &caller_private_key,
506            genesis.hash(),
507            deployment_header,
508            ratifications,
509            None,
510            transactions,
511            aborted_transaction_ids,
512            rng,
513        )
514        .unwrap();
515
516        // Add the deployment block.
517        vm.add_next_block(&deployment_block).unwrap();
518
519        // Fetch the unspent records.
520        let records = deployment_block.records().collect::<indexmap::IndexMap<_, _>>();
521
522        // Prepare the inputs.
523        let inputs = [
524            Value::<CurrentNetwork>::from_str(&address.to_string()).unwrap(),
525            Value::<CurrentNetwork>::from_str("10u64").unwrap(),
526        ]
527        .into_iter();
528
529        // Prepare the fee.
530        let credits = Some(records.values().next().unwrap().decrypt(&caller_view_key).unwrap());
531
532        // Execute.
533        let transaction =
534            vm.execute(&caller_private_key, ("testing.aleo", "initialize"), inputs, credits, 10, None, rng).unwrap();
535
536        // Verify.
537        vm.check_transaction(&transaction, None, rng).unwrap();
538    }
539
540    #[test]
541    fn test_failed_credits_deployment() {
542        let rng = &mut TestRng::default();
543        let vm = crate::vm::test_helpers::sample_vm();
544
545        // Fetch the credits program
546        let program = Program::credits().unwrap();
547
548        // Ensure that the program can't be deployed.
549        assert!(vm.deploy_raw(&program, rng).is_err());
550
551        // Create a new `credits.aleo` program.
552        let program = Program::from_str(
553            r"
554program credits.aleo;
555
556record token:
557    owner as address.private;
558    amount as u64.private;
559
560function compute:
561    input r0 as u32.private;
562    add r0 r0 into r1;
563    output r1 as u32.public;",
564        )
565        .unwrap();
566
567        // Ensure that the program can't be deployed.
568        assert!(vm.deploy_raw(&program, rng).is_err());
569    }
570}