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 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 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 #[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 pub fn exe(&self) -> Arc<VmExe<Val<E::SC>>> {
150 self.instance.exe().clone()
151 }
152
153 pub fn vm(&self) -> &VirtualMachine<E, VB> {
155 &self.instance.vm
156 }
157
158 pub fn vm_config(&self) -> &VB::VmConfig {
160 self.instance.vm.config()
161 }
162}
163
164pub 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
177pub 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
197fn 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}