openvm_sdk/prover/
app.rs

1use std::sync::{Arc, OnceLock};
2
3use getset::Getters;
4use itertools::Itertools;
5use openvm_circuit::{
6    arch::{
7        hasher::poseidon2::{vm_poseidon2_hasher, Poseidon2Hasher},
8        instructions::exe::VmExe,
9        verify_segments, ContinuationVmProof, ContinuationVmProver, Executor, MeteredExecutor,
10        PreflightExecutor, VerifiedExecutionPayload, VirtualMachine, VirtualMachineError,
11        VmBuilder, VmExecutionConfig, VmInstance, VmVerificationError,
12    },
13    system::memory::CHUNK,
14};
15use openvm_stark_backend::{
16    config::{Com, Val},
17    keygen::types::MultiStarkVerifyingKey,
18    p3_field::PrimeField32,
19};
20use openvm_stark_sdk::{
21    config::baby_bear_poseidon2::BabyBearPoseidon2Engine,
22    engine::{StarkEngine, StarkFriEngine},
23};
24use tracing::info_span;
25
26use crate::{
27    commit::{AppExecutionCommit, CommitBytes},
28    keygen::AppVerifyingKey,
29    prover::vm::{new_local_prover, types::VmProvingKey},
30    util::check_max_constraint_degrees,
31    StdIn, F, SC,
32};
33
34#[derive(Getters)]
35pub struct AppProver<E, VB>
36where
37    E: StarkEngine,
38    VB: VmBuilder<E>,
39{
40    pub program_name: Option<String>,
41    #[getset(get = "pub")]
42    instance: VmInstance<E, VB>,
43    #[getset(get = "pub")]
44    app_vm_vk: MultiStarkVerifyingKey<E::SC>,
45    #[getset(get = "pub")]
46    leaf_verifier_program_commit: Com<E::SC>,
47
48    app_execution_commit: OnceLock<AppExecutionCommit>,
49}
50
51impl<E, VB> AppProver<E, VB>
52where
53    E: StarkFriEngine,
54    VB: VmBuilder<E>,
55    Val<E::SC>: PrimeField32,
56    Com<E::SC>: AsRef<[Val<E::SC>; CHUNK]> + From<[Val<E::SC>; CHUNK]> + Into<[Val<E::SC>; CHUNK]>,
57{
58    /// Creates a new [AppProver] instance. This method will re-commit the `exe` program on device.
59    /// If a cached version of the program already exists on device, then directly use the
60    /// [`Self::new_from_instance`] constructor.
61    ///
62    /// The `leaf_verifier_program_commit` is the commitment to the program of the leaf verifier
63    /// that verifies the App VM circuit. It can be found in the `AppProvingKey`.
64    pub fn new(
65        vm_builder: VB,
66        app_vm_pk: &VmProvingKey<E::SC, VB::VmConfig>,
67        app_exe: Arc<VmExe<Val<E::SC>>>,
68        leaf_verifier_program_commit: Com<E::SC>,
69    ) -> Result<Self, VirtualMachineError> {
70        let instance = new_local_prover(vm_builder, app_vm_pk, app_exe)?;
71        let app_vm_vk = app_vm_pk.vm_pk.get_vk();
72
73        Ok(Self::new_from_instance(
74            instance,
75            app_vm_vk,
76            leaf_verifier_program_commit,
77        ))
78    }
79
80    pub fn new_from_instance(
81        instance: VmInstance<E, VB>,
82        app_vm_vk: MultiStarkVerifyingKey<E::SC>,
83        leaf_verifier_program_commit: Com<E::SC>,
84    ) -> Self {
85        Self {
86            program_name: None,
87            instance,
88            app_vm_vk,
89            leaf_verifier_program_commit,
90            app_execution_commit: OnceLock::new(),
91        }
92    }
93
94    pub fn set_program_name(&mut self, program_name: impl AsRef<str>) -> &mut Self {
95        self.program_name = Some(program_name.as_ref().to_string());
96        self
97    }
98    pub fn with_program_name(mut self, program_name: impl AsRef<str>) -> Self {
99        self.set_program_name(program_name);
100        self
101    }
102
103    /// Returns [AppExecutionCommit], which is a commitment to **both** the App VM and the App
104    /// VmExe.
105    pub fn app_commit(&self) -> AppExecutionCommit {
106        *self.app_execution_commit.get_or_init(|| {
107            AppExecutionCommit::compute::<E::SC>(
108                &self.instance().vm.config().as_ref().memory_config,
109                self.instance().exe(),
110                self.instance().program_commitment().clone(),
111                self.leaf_verifier_program_commit.clone(),
112            )
113        })
114    }
115
116    pub fn app_program_commit(&self) -> Com<E::SC> {
117        self.instance().program_commitment().clone()
118    }
119
120    /// Generates proof for every continuation segment
121    ///
122    /// This function internally calls [verify_app_proof] to verify the result before returning the
123    /// proof.
124    pub fn prove(
125        &mut self,
126        input: StdIn<Val<E::SC>>,
127    ) -> Result<ContinuationVmProof<E::SC>, VirtualMachineError>
128    where
129        <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor: Executor<Val<E::SC>>
130            + MeteredExecutor<Val<E::SC>>
131            + PreflightExecutor<Val<E::SC>, VB::RecordArena>,
132    {
133        assert!(
134            self.vm_config().as_ref().continuation_enabled,
135            "Use generate_app_proof_without_continuations instead."
136        );
137        check_max_constraint_degrees(
138            self.vm_config().as_ref(),
139            &self.instance.vm.engine.fri_params(),
140        );
141        let proofs = info_span!(
142            "app proof",
143            group = self
144                .program_name
145                .as_ref()
146                .unwrap_or(&"app_proof".to_string())
147        )
148        .in_scope(|| {
149            #[cfg(feature = "metrics")]
150            metrics::counter!("fri.log_blowup")
151                .absolute(self.instance.vm.engine.fri_params().log_blowup as u64);
152            ContinuationVmProver::prove(&mut self.instance, input)
153        })?;
154        // We skip verification of the user public values proof here because it is directly computed
155        // from the merkle tree above
156        let res = verify_segments(
157            &self.instance.vm.engine,
158            &self.app_vm_vk,
159            &proofs.per_segment,
160        )?;
161        let app_exe_commit_u32s = self.app_commit().app_exe_commit.to_u32_digest();
162        let exe_commit_u32s = res.exe_commit.map(|x| x.as_canonical_u32());
163        if exe_commit_u32s != app_exe_commit_u32s {
164            return Err(VmVerificationError::ExeCommitMismatch {
165                expected: app_exe_commit_u32s,
166                actual: exe_commit_u32s,
167            }
168            .into());
169        }
170        Ok(proofs)
171    }
172
173    /// App Exe
174    pub fn exe(&self) -> Arc<VmExe<Val<E::SC>>> {
175        self.instance.exe().clone()
176    }
177
178    /// App VM
179    pub fn vm(&self) -> &VirtualMachine<E, VB> {
180        &self.instance.vm
181    }
182
183    /// App VM config
184    pub fn vm_config(&self) -> &VB::VmConfig {
185        self.instance.vm.config()
186    }
187}
188
189/// The payload of a verified guest VM execution with user public values extracted and
190/// verified.
191pub struct VerifiedAppArtifacts {
192    /// The Merklelized hash of:
193    /// - Program code commitment (commitment of the cached trace)
194    /// - Merkle root of the initial memory
195    /// - Starting program counter (`pc_start`)
196    ///
197    /// The Merklelization uses Poseidon2 as a cryptographic hash function (for the leaves)
198    /// and a cryptographic compression function (for internal nodes).
199    pub app_exe_commit: CommitBytes,
200    pub user_public_values: Vec<u8>,
201}
202
203/// Verifies the [ContinuationVmProof], which is a collection of STARK proofs as well as
204/// additional Merkle proof for user public values.
205///
206/// This function verifies the STARK proofs and additional conditions to ensure that the
207/// `proof` is a valid proof of guest VM execution that terminates successfully (exit code 0)
208/// _with respect to_ a commitment to some VM executable.
209/// It is the responsibility of the caller to check that the commitment matches the expected
210/// VM executable.
211pub fn verify_app_proof(
212    app_vk: &AppVerifyingKey,
213    proof: &ContinuationVmProof<SC>,
214) -> Result<VerifiedAppArtifacts, VmVerificationError> {
215    static POSEIDON2_HASHER: OnceLock<Poseidon2Hasher<F>> = OnceLock::new();
216    let engine = BabyBearPoseidon2Engine::new(app_vk.fri_params);
217    let VerifiedExecutionPayload {
218        exe_commit,
219        final_memory_root,
220    } = verify_segments(&engine, &app_vk.vk, &proof.per_segment)?;
221
222    proof.user_public_values.verify(
223        POSEIDON2_HASHER.get_or_init(vm_poseidon2_hasher),
224        app_vk.memory_dimensions,
225        final_memory_root,
226    )?;
227
228    let app_exe_commit = CommitBytes::from_u32_digest(&exe_commit.map(|x| x.as_canonical_u32()));
229    // The user public values address space has cells have type u8
230    let user_public_values = proof
231        .user_public_values
232        .public_values
233        .iter()
234        .map(|x| x.as_canonical_u32().try_into().unwrap())
235        .collect_vec();
236    Ok(VerifiedAppArtifacts {
237        app_exe_commit,
238        user_public_values,
239    })
240}