openvm_sdk/prover/
app.rs

1use std::sync::{Arc, OnceLock};
2
3use getset::Getters;
4use openvm_circuit::{
5    arch::{
6        hasher::poseidon2::{vm_poseidon2_hasher, Poseidon2Hasher},
7        instructions::exe::VmExe,
8        verify_segments, ContinuationVmProof, ContinuationVmProver, Executor, MeteredExecutor,
9        PreflightExecutor, VerifiedExecutionPayload, VirtualMachine, VirtualMachineError,
10        VmBuilder, VmExecutionConfig, VmInstance, VmVerificationError,
11    },
12    system::{
13        memory::dimensions::MemoryDimensions, program::trace::compute_exe_commit_from_mem_config,
14    },
15};
16use openvm_continuations::CommitBytes;
17use openvm_stark_backend::{
18    keygen::types::MultiStarkVerifyingKey, p3_field::PrimeField32, prover::ProverBackend,
19    StarkEngine, Val,
20};
21use openvm_stark_sdk::config::baby_bear_poseidon2::Digest;
22use tracing::instrument;
23
24use crate::{
25    keygen::AppVerifyingKey,
26    prover::vm::{new_local_prover, types::VmProvingKey},
27    util::check_max_constraint_degrees,
28    SdkError, StdIn, F, SC,
29};
30
31#[derive(Getters)]
32pub struct AppProver<E, VB>
33where
34    E: StarkEngine,
35    VB: VmBuilder<E>,
36{
37    pub program_name: Option<String>,
38    #[getset(get = "pub")]
39    instance: VmInstance<E, VB>,
40    #[getset(get = "pub")]
41    app_vm_vk: MultiStarkVerifyingKey<E::SC>,
42    app_exe_commit: OnceLock<Digest>,
43}
44
45impl<E, VB> AppProver<E, VB>
46where
47    E: StarkEngine<SC = SC>,
48    VB: VmBuilder<E>,
49    Val<E::SC>: PrimeField32,
50{
51    /// Creates a new [AppProver] instance. This method will re-commit the `exe` program on device.
52    /// If a cached version of the program already exists on device, then directly use the
53    /// [`Self::new_from_instance`] constructor.
54    ///
55    /// The `leaf_verifier_program_commit` is the commitment to the program of the leaf verifier
56    /// that verifies the App VM circuit. It can be found in the `AppProvingKey`.
57    pub fn new(
58        vm_builder: VB,
59        app_vm_pk: &VmProvingKey<VB::VmConfig>,
60        app_exe: Arc<VmExe<Val<E::SC>>>,
61    ) -> Result<Self, VirtualMachineError> {
62        let instance = new_local_prover(vm_builder, app_vm_pk, app_exe)?;
63        let app_vm_vk = app_vm_pk.vm_pk.get_vk();
64        Ok(Self::new_from_instance(instance, app_vm_vk))
65    }
66
67    pub fn new_from_instance(
68        instance: VmInstance<E, VB>,
69        app_vm_vk: MultiStarkVerifyingKey<E::SC>,
70    ) -> Self {
71        Self {
72            program_name: None,
73            instance,
74            app_vm_vk,
75            app_exe_commit: OnceLock::new(),
76        }
77    }
78
79    pub fn set_program_name(&mut self, program_name: impl AsRef<str>) -> &mut Self {
80        self.program_name = Some(program_name.as_ref().to_string());
81        self
82    }
83
84    pub fn with_program_name(mut self, program_name: impl AsRef<str>) -> Self {
85        self.set_program_name(program_name);
86        self
87    }
88
89    pub fn app_program_commit(&self) -> <E::PB as ProverBackend>::Commitment {
90        *self.instance().program_commitment()
91    }
92
93    /// Returns commitment to the executable
94    pub fn app_exe_commit(&self) -> Digest {
95        *self.app_exe_commit.get_or_init(|| {
96            compute_exe_commit_from_mem_config(
97                &self.app_program_commit(),
98                self.instance.exe(),
99                &self.instance.vm.config().as_ref().memory_config,
100            )
101        })
102    }
103
104    pub fn memory_dimensions(&self) -> MemoryDimensions {
105        self.instance
106            .vm
107            .config()
108            .as_ref()
109            .memory_config
110            .memory_dimensions()
111    }
112
113    pub fn num_user_pvs(&self) -> usize {
114        self.instance.vm.config().as_ref().num_public_values
115    }
116
117    /// Generates proof for every continuation segment
118    #[instrument(
119        name = "app_prove",
120        skip_all,
121        fields(group = self.program_name.as_ref().unwrap_or(&"app_proof".to_string()))
122    )]
123    pub fn prove(
124        &mut self,
125        input: StdIn<Val<E::SC>>,
126    ) -> Result<ContinuationVmProof<E::SC>, VirtualMachineError>
127    where
128        <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor: Executor<Val<E::SC>>
129            + MeteredExecutor<Val<E::SC>>
130            + PreflightExecutor<Val<E::SC>, VB::RecordArena>,
131    {
132        check_max_constraint_degrees(
133            self.vm_config().as_ref(),
134            self.app_vm_vk.inner.max_constraint_degree(),
135        );
136        let proof = ContinuationVmProver::prove(&mut self.instance, input)?;
137        #[cfg(debug_assertions)]
138        let _ = verify_app_proof_inner::<E>(
139            &self.app_vm_vk,
140            self.memory_dimensions(),
141            self.num_user_pvs(),
142            &proof,
143        )
144        .expect("app proof verification failed");
145        Ok(proof)
146    }
147
148    /// App Exe
149    pub fn exe(&self) -> Arc<VmExe<Val<E::SC>>> {
150        self.instance.exe().clone()
151    }
152
153    /// App VM
154    pub fn vm(&self) -> &VirtualMachine<E, VB> {
155        &self.instance.vm
156    }
157
158    /// App VM config
159    pub fn vm_config(&self) -> &VB::VmConfig {
160        self.instance.vm.config()
161    }
162}
163
164/// Verifies a ContinuationVmProof and returns the app_exe_commit
165pub fn verify_app_proof<E: StarkEngine<SC = SC>>(
166    app_vk: &AppVerifyingKey,
167    proof: &ContinuationVmProof<E::SC>,
168) -> Result<Digest, SdkError> {
169    verify_app_proof_inner::<E>(
170        &app_vk.vk,
171        app_vk.memory_dimensions,
172        app_vk.num_user_pvs,
173        proof,
174    )
175}
176
177/// Verifies a ContinuationVmProof and optionally checks that the recovered app_exe_commit matches
178/// the expected value.
179pub fn verify_app_proof_with_expected_exe_commit<E: StarkEngine<SC = SC>>(
180    app_vk: &AppVerifyingKey,
181    proof: &ContinuationVmProof<E::SC>,
182    expected_exe_commit: Option<Digest>,
183) -> Result<(), SdkError> {
184    let exe_commit = verify_app_proof::<E>(app_vk, proof)?;
185    if let Some(expected_exe_commit) = expected_exe_commit {
186        if exe_commit != expected_exe_commit {
187            return Err(SdkError::Other(eyre::eyre!(
188                "app proof exe commit mismatch: expected {}, actual {}",
189                CommitBytes::from(expected_exe_commit),
190                CommitBytes::from(exe_commit)
191            )));
192        }
193    }
194    Ok(())
195}
196
197/// Verifies a ContinuationVmProof from the borrowed components of an
198/// [`AppVerifyingKey`], returning the app_exe_commit.
199fn verify_app_proof_inner<E: StarkEngine<SC = SC>>(
200    vk: &MultiStarkVerifyingKey<SC>,
201    memory_dimensions: MemoryDimensions,
202    num_user_pvs: usize,
203    proof: &ContinuationVmProof<E::SC>,
204) -> Result<Digest, SdkError> {
205    static POSEIDON2_HASHER: OnceLock<Poseidon2Hasher<F>> = OnceLock::new();
206    let engine = E::new(vk.inner.params.clone());
207    let VerifiedExecutionPayload {
208        exe_commit,
209        final_memory_root,
210    } = verify_segments(&engine, vk, &proof.per_segment)?;
211
212    if proof.user_public_values.public_values.len() != num_user_pvs {
213        return Err(SdkError::Other(eyre::eyre!(
214            "wrong number of user public values (expected: {}, actual: {})",
215            num_user_pvs,
216            proof.user_public_values.public_values.len()
217        )));
218    }
219
220    proof
221        .user_public_values
222        .verify(
223            POSEIDON2_HASHER.get_or_init(vm_poseidon2_hasher),
224            memory_dimensions,
225            final_memory_root,
226        )
227        .map_err(VmVerificationError::from)?;
228
229    Ok(exe_commit)
230}