snarkvm_synthesizer_process/
lib.rs

1// Copyright (c) 2019-2025 Provable 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
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#![forbid(unsafe_code)]
17#![allow(clippy::too_many_arguments)]
18// #![warn(clippy::cast_possible_truncation)]
19// TODO (howardwu): Update the return type on `execute` after stabilizing the interface.
20#![allow(clippy::type_complexity)]
21
22extern crate snarkvm_circuit as circuit;
23extern crate snarkvm_console as console;
24
25mod cost;
26pub use cost::*;
27
28mod stack;
29pub use stack::*;
30
31mod trace;
32pub use trace::*;
33
34mod authorize;
35mod deploy;
36mod evaluate;
37mod execute;
38mod finalize;
39mod verify_deployment;
40mod verify_execution;
41mod verify_fee;
42
43#[cfg(test)]
44mod tests;
45
46use console::{
47    account::PrivateKey,
48    network::prelude::*,
49    program::{
50        Identifier,
51        Literal,
52        Locator,
53        Plaintext,
54        ProgramID,
55        Record,
56        Request,
57        Response,
58        Value,
59        compute_function_id,
60    },
61    types::{Field, U16, U64},
62};
63use snarkvm_algorithms::snark::varuna::VarunaVersion;
64use snarkvm_ledger_block::{Deployment, Execution, Fee, Input, Output, Transaction, Transition};
65use snarkvm_ledger_store::{FinalizeStorage, FinalizeStore, atomic_batch_scope};
66use snarkvm_synthesizer_program::{
67    Branch,
68    Command,
69    FinalizeGlobalState,
70    FinalizeOperation,
71    Instruction,
72    Program,
73    StackTrait,
74};
75use snarkvm_synthesizer_snark::{ProvingKey, UniversalSRS, VerifyingKey};
76use snarkvm_utilities::{defer, dev_println};
77
78use aleo_std::prelude::{finish, lap, timer};
79use indexmap::IndexMap;
80#[cfg(feature = "locktick")]
81use locktick::parking_lot::RwLock;
82#[cfg(not(feature = "locktick"))]
83use parking_lot::RwLock;
84use std::{collections::HashMap, sync::Arc};
85
86#[derive(Clone)]
87pub struct Process<N: Network> {
88    /// The universal SRS.
89    universal_srs: UniversalSRS<N>,
90    /// The mapping of program IDs to stacks.
91    stacks: Arc<RwLock<IndexMap<ProgramID<N>, Arc<Stack<N>>>>>,
92    /// The mapping of program IDs to old stacks.
93    old_stacks: Arc<RwLock<IndexMap<ProgramID<N>, Option<Arc<Stack<N>>>>>>,
94}
95
96impl<N: Network> Process<N> {
97    /// Initializes a new process.
98    #[inline]
99    pub fn setup<A: circuit::Aleo<Network = N>, R: Rng + CryptoRng>(rng: &mut R) -> Result<Self> {
100        let timer = timer!("Process:setup");
101
102        // Initialize the process.
103        let mut process =
104            Self { universal_srs: UniversalSRS::load()?, stacks: Default::default(), old_stacks: Default::default() };
105        lap!(timer, "Initialize process");
106
107        // Initialize the 'credits.aleo' program.
108        let program = Program::credits()?;
109        lap!(timer, "Load credits program");
110
111        // Compute the 'credits.aleo' program stack.
112        let stack = Stack::new(&process, &program)?;
113        lap!(timer, "Initialize stack");
114
115        // Synthesize the 'credits.aleo' circuit keys.
116        for function_name in program.functions().keys() {
117            stack.synthesize_key::<A, _>(function_name, rng)?;
118            lap!(timer, "Synthesize circuit keys for {function_name}");
119        }
120        lap!(timer, "Synthesize credits program keys");
121
122        // Add the 'credits.aleo' stack to the process.
123        process.add_stack(stack);
124
125        finish!(timer);
126        // Return the process.
127        Ok(process)
128    }
129
130    /// Adds a new stack to the process.
131    /// If the program already exists, then the existing stack is replaced and the original stack is returned.
132    /// Note. This method assumes that the provided stack is valid.
133    #[inline]
134    pub fn add_stack(&mut self, stack: Stack<N>) -> Option<Arc<Stack<N>>> {
135        // Get the program ID.
136        let program_id = *stack.program_id();
137        // Arc the stack first to limit the scope of the write lock.
138        let stack = Arc::new(stack);
139        // Insert the stack into the process, replacing the existing stack if it exists.
140        self.stacks.write().insert(program_id, stack)
141    }
142
143    /// Stages a stack to be added to the process.
144    /// The new stack is active, while the old stack is retained in `old_stacks`.
145    /// The `commit_stacks` method must be called to finalize the addition of the new stack.
146    /// The `revert_stacks` method can be called to revert the staged stacks.
147    #[inline]
148    pub fn stage_stack(&self, stack: Stack<N>) {
149        // Get the program ID.
150        let program_id = *stack.program_id();
151        // Arc the stack first to limit the scope of the write lock.
152        let stack = Arc::new(stack);
153        // If no entry in `old_stacks` exists for `program_id`, store the old stack.
154        // Note: If `old_stack` is `None`, it means that we are adding a new program to the process.
155        let old_stack = self.stacks.write().insert(program_id, stack);
156        let mut old_stacks = self.old_stacks.write();
157        if !old_stacks.contains_key(&program_id) {
158            old_stacks.insert(program_id, old_stack);
159        }
160    }
161
162    /// Commits the staged stacks to the process.
163    /// This finalizes the addition of the new stacks and clears the old stacks.
164    #[inline]
165    pub fn commit_stacks(&self) {
166        // Clear the old stacks.
167        self.old_stacks.write().clear();
168    }
169
170    /// Reverts the staged stacks, restoring the previous state of the process.
171    /// This will remove the new stacks and restore the old stacks.
172    #[inline]
173    pub fn revert_stacks(&self) {
174        // Restore the old stacks.
175        for (program_id, stack) in self.old_stacks.write().drain(..) {
176            // If the stack is `None`, remove the program from the process.
177            // Otherwise, insert the old stack back into the process.
178            if let Some(stack) = stack {
179                self.stacks.write().insert(program_id, stack);
180            } else {
181                self.stacks.write().shift_remove(&program_id);
182            }
183        }
184    }
185}
186
187impl<N: Network> Process<N> {
188    /// Initializes a new process.
189    #[inline]
190    pub fn load() -> Result<Self> {
191        let timer = timer!("Process::load");
192
193        // Initialize the process.
194        let mut process =
195            Self { universal_srs: UniversalSRS::load()?, stacks: Default::default(), old_stacks: Default::default() };
196        lap!(timer, "Initialize process");
197
198        // Initialize the 'credits.aleo' program.
199        let program = Program::credits()?;
200        lap!(timer, "Load credits program");
201
202        // Compute the 'credits.aleo' program stack.
203        let stack = Stack::new(&process, &program)?;
204        lap!(timer, "Initialize stack");
205
206        // Synthesize the 'credits.aleo' verifying keys.
207        for function_name in program.functions().keys() {
208            // Load the verifying key.
209            let verifying_key = N::get_credits_verifying_key(function_name.to_string())?;
210            // Retrieve the number of public and private variables.
211            // Note: This number does *NOT* include the number of constants. This is safe because
212            // this program is never deployed, as it is a first-class citizen of the protocol.
213            let num_variables = verifying_key.circuit_info.num_public_and_private_variables as u64;
214            // Insert the verifying key.
215            stack.insert_verifying_key(function_name, VerifyingKey::new(verifying_key.clone(), num_variables))?;
216            lap!(timer, "Load verifying key for {function_name}");
217        }
218        lap!(timer, "Load circuit keys");
219
220        // Add the stack to the process.
221        process.add_stack(stack);
222
223        finish!(timer, "Process::load");
224        // Return the process.
225        Ok(process)
226    }
227
228    /// Initializes a new process with the V0 credits.aleo verifiying keys.
229    #[inline]
230    pub fn load_v0() -> Result<Self> {
231        let timer = timer!("Process::load_v0");
232
233        // Initialize the process.
234        let mut process =
235            Self { universal_srs: UniversalSRS::load()?, stacks: Default::default(), old_stacks: Default::default() };
236        lap!(timer, "Initialize process");
237
238        // Initialize the 'credits.aleo' program.
239        let program = Program::credits()?;
240        lap!(timer, "Load credits program");
241
242        // Compute the 'credits.aleo' program stack.
243        let stack = Stack::new(&process, &program)?;
244        lap!(timer, "Initialize stack");
245
246        // Synthesize the 'credits.aleo' verifying keys.
247        for function_name in program.functions().keys() {
248            // Load the verifying key.
249            let verifying_key = N::get_credits_v0_verifying_key(function_name.to_string())?;
250            // Retrieve the number of public and private variables.
251            // Note: This number does *NOT* include the number of constants. This is safe because
252            // this program is never deployed, as it is a first-class citizen of the protocol.
253            let num_variables = verifying_key.circuit_info.num_public_and_private_variables as u64;
254            // Insert the verifying key.
255            stack.insert_verifying_key(function_name, VerifyingKey::new(verifying_key.clone(), num_variables))?;
256            lap!(timer, "Load verifying key for {function_name}");
257        }
258        lap!(timer, "Load circuit keys");
259
260        // Add the stack to the process.
261        process.add_stack(stack);
262
263        finish!(timer, "Process::load_v0");
264        // Return the process.
265        Ok(process)
266    }
267
268    /// Initializes a new process without downloading the 'credits.aleo' circuit keys (for web contexts).
269    #[inline]
270    #[cfg(feature = "wasm")]
271    pub fn load_web() -> Result<Self> {
272        // Initialize the process.
273        let mut process =
274            Self { universal_srs: UniversalSRS::load()?, stacks: Default::default(), old_stacks: Default::default() };
275
276        // Initialize the 'credits.aleo' program.
277        let program = Program::credits()?;
278
279        // Compute the 'credits.aleo' program stack.
280        let stack = Stack::new(&process, &program)?;
281
282        // Add the stack to the process.
283        process.add_stack(stack);
284
285        // Return the process.
286        Ok(process)
287    }
288
289    /// Adds a new program to the process, verifying that it is a valid addition.
290    /// If the program exists, then the existing stack is replaced and discarded.
291    /// Note. This method should **NOT** be used by the on-chain VM to add new program, use `finalize_deployment` or `load_deployment` instead instead.
292    #[inline]
293    pub fn add_program(&mut self, program: &Program<N>) -> Result<()> {
294        // Initialize the 'credits.aleo' program ID.
295        let credits_program_id = ProgramID::<N>::from_str("credits.aleo")?;
296        // If the program is not 'credits.aleo', compute the program stack, and add it to the process.
297        if program.id() != &credits_program_id {
298            self.add_stack(Stack::new(self, program)?);
299        }
300        Ok(())
301    }
302
303    /// Adds a new program with the given edition to the process, verifying that it is a valid addition.
304    /// If the program exists, then the existing stack is replaced and discarded.
305    /// Note. This method should **NOT** be used by the on-chain VM to add new program, use `finalize_deployment` or `load_deployment` instead instead.
306    #[inline]
307    pub fn add_program_with_edition(&mut self, program: &Program<N>, edition: u16) -> Result<()> {
308        // Initialize the 'credits.aleo' program ID.
309        let credits_program_id = ProgramID::<N>::from_str("credits.aleo")?;
310        // If the program is not 'credits.aleo', compute the program stack, and add it to the process.
311        if program.id() != &credits_program_id {
312            let stack = Stack::new_raw(self, program, edition)?;
313            stack.initialize_and_check(self)?;
314            self.add_stack(stack);
315        }
316        Ok(())
317    }
318
319    /// Adds a set of programs and editions, in topological order, to the process, deferring validation of the programs until all programs are added.
320    /// If a program exists, then the existing stack is replaced and discarded.
321    /// Either all programs are added or none are.
322    /// Note. This method should **NOT** be used by the on-chain VM to add new program, use `finalize_deployment` or `load_deployment` instead instead.
323    #[inline]
324    pub fn add_programs_with_editions(&mut self, programs: &[(Program<N>, u16)]) -> Result<()> {
325        // Initialize the 'credits.aleo' program ID.
326        let credits_program_id = ProgramID::<N>::from_str("credits.aleo")?;
327        // Defer cleanup of the uncommitted stacks.
328        defer! {
329            self.revert_stacks()
330        }
331        // Initialize raw stacks for each of the programs, skipping `credits.aleo`.
332        for (program, edition) in programs {
333            if program.id() != &credits_program_id {
334                self.stage_stack(Stack::new_raw(self, program, *edition)?)
335            }
336        }
337        // For each stack, check and initialize it before adding it to the process.
338        for (program, _) in programs {
339            // Retrieve the stack.
340            let stack = self.get_stack(program.id())?;
341            // Initialize and check the stack for well-formedness.
342            stack.initialize_and_check(self)?;
343        }
344        // Commit the staged stacks.
345        self.commit_stacks();
346        Ok(())
347    }
348
349    /// Returns the universal SRS.
350    #[inline]
351    pub const fn universal_srs(&self) -> &UniversalSRS<N> {
352        &self.universal_srs
353    }
354
355    /// Returns `true` if the process contains the program with the given ID.
356    #[inline]
357    pub fn contains_program(&self, program_id: &ProgramID<N>) -> bool {
358        self.stacks.read().contains_key(program_id)
359    }
360
361    /// Returns the program IDs of all programs in the process.
362    #[inline]
363    pub fn program_ids(&self) -> Vec<ProgramID<N>> {
364        self.stacks.read().keys().copied().collect()
365    }
366
367    /// Returns the stack for the given program ID.
368    #[inline]
369    pub fn get_stack(&self, program_id: impl TryInto<ProgramID<N>>) -> Result<Arc<Stack<N>>> {
370        // Prepare the program ID.
371        let program_id = program_id.try_into().map_err(|_| anyhow!("Invalid program ID"))?;
372        // Retrieve the stack.
373        let stack = self
374            .stacks
375            .read()
376            .get(&program_id)
377            .ok_or_else(|| anyhow!("Program '{program_id}' does not exist"))?
378            .clone();
379        // Ensure the program ID matches.
380        ensure!(stack.program_id() == &program_id, "Expected program '{}', found '{program_id}'", stack.program_id());
381        // Return the stack.
382        Ok(stack)
383    }
384
385    /// Returns the proving key for the given program ID and function name.
386    #[inline]
387    pub fn get_proving_key(
388        &self,
389        program_id: impl TryInto<ProgramID<N>>,
390        function_name: impl TryInto<Identifier<N>>,
391    ) -> Result<ProvingKey<N>> {
392        // Prepare the function name.
393        let function_name = function_name.try_into().map_err(|_| anyhow!("Invalid function name"))?;
394        // Return the proving key.
395        self.get_stack(program_id)?.get_proving_key(&function_name)
396    }
397
398    /// Returns the verifying key for the given program ID and function name.
399    #[inline]
400    pub fn get_verifying_key(
401        &self,
402        program_id: impl TryInto<ProgramID<N>>,
403        function_name: impl TryInto<Identifier<N>>,
404    ) -> Result<VerifyingKey<N>> {
405        // Prepare the function name.
406        let function_name = function_name.try_into().map_err(|_| anyhow!("Invalid function name"))?;
407        // Return the verifying key.
408        self.get_stack(program_id)?.get_verifying_key(&function_name)
409    }
410
411    /// Inserts the given proving key, for the given program ID and function name.
412    #[inline]
413    pub fn insert_proving_key(
414        &self,
415        program_id: &ProgramID<N>,
416        function_name: &Identifier<N>,
417        proving_key: ProvingKey<N>,
418    ) -> Result<()> {
419        self.get_stack(program_id)?.insert_proving_key(function_name, proving_key)
420    }
421
422    /// Removes the given proving key, for the given program ID and function name.
423    #[inline]
424    pub fn remove_proving_key(&self, program_id: &ProgramID<N>, function_name: &Identifier<N>) -> Result<()> {
425        self.get_stack(program_id)?.remove_proving_key(function_name);
426        Ok(())
427    }
428
429    /// Inserts the given verifying key, for the given program ID and function name.
430    #[inline]
431    pub fn insert_verifying_key(
432        &self,
433        program_id: &ProgramID<N>,
434        function_name: &Identifier<N>,
435        verifying_key: VerifyingKey<N>,
436    ) -> Result<()> {
437        self.get_stack(program_id)?.insert_verifying_key(function_name, verifying_key)
438    }
439
440    /// Removes the given verifying key, for the given program ID and function name.
441    #[inline]
442    pub fn remove_verifying_key(&self, program_id: &ProgramID<N>, function_name: &Identifier<N>) -> Result<()> {
443        self.get_stack(program_id)?.remove_verifying_key(function_name);
444        Ok(())
445    }
446
447    /// Synthesizes the proving and verifying key for the given program ID and function name.
448    #[inline]
449    pub fn synthesize_key<A: circuit::Aleo<Network = N>, R: Rng + CryptoRng>(
450        &self,
451        program_id: &ProgramID<N>,
452        function_name: &Identifier<N>,
453        rng: &mut R,
454    ) -> Result<()> {
455        // Synthesize the proving and verifying key.
456        self.get_stack(program_id)?.synthesize_key::<A, R>(function_name, rng)
457    }
458}
459
460#[cfg(test)]
461pub mod test_helpers {
462    use super::*;
463    use console::{account::PrivateKey, network::MainnetV0, program::Identifier};
464    use snarkvm_ledger_block::Transition;
465    use snarkvm_ledger_query::Query;
466    use snarkvm_ledger_store::{BlockStore, helpers::memory::BlockMemory};
467    use snarkvm_synthesizer_program::Program;
468
469    use aleo_std::StorageMode;
470    use std::sync::OnceLock;
471
472    type CurrentNetwork = MainnetV0;
473    type CurrentAleo = circuit::network::AleoV0;
474
475    /// Returns an execution for the given program and function name.
476    pub fn get_execution(
477        process: &mut Process<CurrentNetwork>,
478        program: &Program<CurrentNetwork>,
479        function_name: &Identifier<CurrentNetwork>,
480        inputs: impl ExactSizeIterator<Item = impl TryInto<Value<CurrentNetwork>>>,
481    ) -> Execution<CurrentNetwork> {
482        // Initialize a new rng.
483        let rng = &mut TestRng::default();
484
485        // Initialize a private key.
486        let private_key = PrivateKey::new(rng).unwrap();
487
488        // Add the program to the process if doesn't yet exist.
489        if !process.contains_program(program.id()) {
490            process.add_program(program).unwrap();
491        }
492
493        // Compute the authorization.
494        let authorization =
495            process.authorize::<CurrentAleo, _>(&private_key, program.id(), function_name, inputs, rng).unwrap();
496
497        // Execute the program.
498        let (_, mut trace) = process.execute::<CurrentAleo, _>(authorization, rng).unwrap();
499
500        // Initialize a new block store.
501        let block_store = BlockStore::<CurrentNetwork, BlockMemory<_>>::open(StorageMode::new_test(None)).unwrap();
502
503        // Prepare the assignments from the block store.
504        trace.prepare(&snarkvm_ledger_query::Query::from(block_store)).unwrap();
505
506        // Get the locator.
507        let locator = format!("{:?}:{function_name:?}", program.id());
508
509        // Return the execution object.
510        trace.prove_execution::<CurrentAleo, _>(&locator, VarunaVersion::V1, rng).unwrap()
511    }
512
513    pub fn sample_key() -> (Identifier<CurrentNetwork>, ProvingKey<CurrentNetwork>, VerifyingKey<CurrentNetwork>) {
514        static INSTANCE: OnceLock<(
515            Identifier<CurrentNetwork>,
516            ProvingKey<CurrentNetwork>,
517            VerifyingKey<CurrentNetwork>,
518        )> = OnceLock::new();
519        INSTANCE
520            .get_or_init(|| {
521                // Initialize a new program.
522                let (string, program) = Program::<CurrentNetwork>::parse(
523                    r"
524program testing.aleo;
525
526function compute:
527    input r0 as u32.private;
528    input r1 as u32.public;
529    add r0 r1 into r2;
530    output r2 as u32.public;",
531                )
532                .unwrap();
533                assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
534
535                // Declare the function name.
536                let function_name = Identifier::from_str("compute").unwrap();
537
538                // Initialize the RNG.
539                let rng = &mut TestRng::default();
540
541                // Construct the process.
542                let process = sample_process(&program);
543
544                // Synthesize a proving and verifying key.
545                process.synthesize_key::<CurrentAleo, _>(program.id(), &function_name, rng).unwrap();
546
547                // Get the proving and verifying key.
548                let proving_key = process.get_proving_key(program.id(), function_name).unwrap();
549                let verifying_key = process.get_verifying_key(program.id(), function_name).unwrap();
550
551                (function_name, proving_key, verifying_key)
552            })
553            .clone()
554    }
555
556    pub(crate) fn sample_execution() -> Execution<CurrentNetwork> {
557        static INSTANCE: OnceLock<Execution<CurrentNetwork>> = OnceLock::new();
558        INSTANCE
559            .get_or_init(|| {
560                // Initialize a new program.
561                let (string, program) = Program::<CurrentNetwork>::parse(
562                    r"
563program testing.aleo;
564
565function compute:
566    input r0 as u32.private;
567    input r1 as u32.public;
568    add r0 r1 into r2;
569    output r2 as u32.public;",
570                )
571                .unwrap();
572                assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
573
574                // Declare the function name.
575                let function_name = Identifier::from_str("compute").unwrap();
576
577                // Initialize the RNG.
578                let rng = &mut TestRng::default();
579                // Initialize a new caller account.
580                let caller_private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
581
582                // Initialize a new block store.
583                let block_store =
584                    BlockStore::<CurrentNetwork, BlockMemory<_>>::open(StorageMode::new_test(None)).unwrap();
585
586                // Construct the process.
587                let process = sample_process(&program);
588                // Authorize the function call.
589                let authorization = process
590                    .authorize::<CurrentAleo, _>(
591                        &caller_private_key,
592                        program.id(),
593                        function_name,
594                        ["5u32", "10u32"].into_iter(),
595                        rng,
596                    )
597                    .unwrap();
598                assert_eq!(authorization.len(), 1);
599                // Execute the request.
600                let (_response, mut trace) = process.execute::<CurrentAleo, _>(authorization, rng).unwrap();
601                assert_eq!(trace.transitions().len(), 1);
602
603                // Prepare the trace.
604                trace.prepare(&Query::from(block_store)).unwrap();
605                // Compute the execution.
606                trace.prove_execution::<CurrentAleo, _>("testing", VarunaVersion::V1, rng).unwrap()
607            })
608            .clone()
609    }
610
611    pub fn sample_transition() -> Transition<CurrentNetwork> {
612        // Retrieve the execution.
613        let mut execution = sample_execution();
614        // Ensure the execution is not empty.
615        assert!(!execution.is_empty());
616        // Return the transition.
617        execution.pop().unwrap()
618    }
619
620    /// Initializes a new process with the given program.
621    pub(crate) fn sample_process(program: &Program<CurrentNetwork>) -> Process<CurrentNetwork> {
622        // Construct a new process.
623        let mut process = Process::load().unwrap();
624        // Add the program to the process.
625        process.add_program(program).unwrap();
626        // Return the process.
627        process
628    }
629}