1#![cfg_attr(feature = "tco", allow(incomplete_features))]
2#![cfg_attr(feature = "tco", feature(explicit_tail_calls))]
3use std::{
4 borrow::Borrow,
5 fs::read,
6 marker::PhantomData,
7 path::Path,
8 sync::{Arc, OnceLock},
9};
10
11#[cfg(feature = "evm-verify")]
12use alloy_sol_types::sol;
13use commit::AppExecutionCommit;
14use config::{AggregationTreeConfig, AppConfig};
15use getset::{Getters, MutGetters, WithSetters};
16use keygen::{AppProvingKey, AppVerifyingKey};
17use openvm_build::{
18 build_guest_package, find_unique_executable, get_package, GuestOptions, TargetFilter,
19};
20use openvm_circuit::{
21 arch::{
22 execution_mode::Segment,
23 hasher::{poseidon2::vm_poseidon2_hasher, Hasher},
24 instructions::exe::VmExe,
25 Executor, InitFileGenerator, MeteredExecutor, PreflightExecutor, VirtualMachineError,
26 VmBuilder, VmExecutionConfig, VmExecutor, VmVerificationError, CONNECTOR_AIR_ID,
27 PROGRAM_AIR_ID, PROGRAM_CACHED_TRACE_INDEX, PUBLIC_VALUES_AIR_ID,
28 },
29 system::{
30 memory::{
31 merkle::public_values::{extract_public_values, UserPublicValuesProofError},
32 CHUNK,
33 },
34 program::trace::compute_exe_commit,
35 },
36};
37#[cfg(feature = "evm-prove")]
38pub use openvm_continuations::static_verifier::DefaultStaticVerifierPvHandler;
39use openvm_continuations::verifier::{
40 common::types::VmVerifierPvs,
41 internal::types::{InternalVmVerifierPvs, VmStarkProof},
42 root::RootVmVerifierConfig,
43};
44pub use openvm_continuations::{RootSC, C, F, SC};
46use openvm_native_circuit::{NativeConfig, NativeCpuBuilder};
47use openvm_native_compiler::conversion::CompilerOptions;
48#[cfg(feature = "evm-prove")]
49use openvm_native_recursion::halo2::utils::{CacheHalo2ParamsReader, Halo2ParamsReader};
50use openvm_stark_backend::proof::Proof;
51use openvm_stark_sdk::{
52 config::baby_bear_poseidon2::BabyBearPoseidon2Engine,
53 engine::{StarkEngine, StarkFriEngine},
54};
55use openvm_transpiler::{
56 elf::Elf, openvm_platform::memory::MEM_SIZE, transpiler::Transpiler, FromElf,
57};
58#[cfg(feature = "evm-verify")]
59use snark_verifier_sdk::{evm::gen_evm_verifier_sol_code, halo2::aggregation::AggregationCircuit};
60
61#[cfg(feature = "evm-prove")]
62use crate::{
63 config::Halo2Config, keygen::Halo2ProvingKey, prover::EvmHalo2Prover, types::EvmProof,
64};
65use crate::{
66 config::{AggregationConfig, SdkVmConfig, SdkVmCpuBuilder, TranspilerConfig},
67 keygen::{asm::program_to_asm, AggProvingKey, AggVerifyingKey},
68 prover::{AppProver, StarkProver},
69 types::ExecutableFormat,
70};
71
72cfg_if::cfg_if! {
73 if #[cfg(feature = "cuda")] {
74 use config::SdkVmGpuBuilder;
75 use openvm_cuda_backend::engine::GpuBabyBearPoseidon2Engine;
76 use openvm_native_circuit::NativeGpuBuilder;
77 pub use GpuSdk as Sdk;
78 pub type DefaultStarkEngine = GpuBabyBearPoseidon2Engine;
79 } else {
80 pub use CpuSdk as Sdk;
81 pub type DefaultStarkEngine = BabyBearPoseidon2Engine;
82 }
83}
84
85pub mod codec;
86pub mod commit;
87pub mod config;
88pub mod fs;
89pub mod keygen;
90pub mod prover;
91pub mod types;
92pub mod util;
93
94mod error;
95mod stdin;
96pub use error::SdkError;
97pub use stdin::*;
98
99pub const EVM_HALO2_VERIFIER_INTERFACE: &str =
100 include_str!("../contracts/src/IOpenVmHalo2Verifier.sol");
101pub const EVM_HALO2_VERIFIER_TEMPLATE: &str =
102 include_str!("../contracts/template/OpenVmHalo2Verifier.sol");
103pub const OPENVM_VERSION: &str = concat!(
104 env!("CARGO_PKG_VERSION_MAJOR"),
105 ".",
106 env!("CARGO_PKG_VERSION_MINOR")
107);
108
109#[cfg(feature = "evm-verify")]
110sol! {
111 IOpenVmHalo2Verifier,
112 concat!(env!("CARGO_MANIFEST_DIR"), "/contracts/abi/IOpenVmHalo2Verifier.json"),
113}
114
115#[derive(Getters, MutGetters, WithSetters)]
127pub struct GenericSdk<E, VB, NativeBuilder>
128where
129 E: StarkEngine<SC = SC>,
130 VB: VmBuilder<E>,
131 VB::VmConfig: VmExecutionConfig<F>,
132{
133 #[getset(get = "pub", get_mut = "pub", set_with = "pub")]
134 app_config: AppConfig<VB::VmConfig>,
135 #[getset(get = "pub", get_mut = "pub", set_with = "pub")]
136 agg_config: AggregationConfig,
137 #[getset(get = "pub", get_mut = "pub", set_with = "pub")]
138 agg_tree_config: AggregationTreeConfig,
139 #[cfg(feature = "evm-prove")]
140 #[getset(get = "pub", get_mut = "pub", set_with = "pub")]
141 halo2_config: Halo2Config,
142
143 #[getset(get = "pub")]
147 executor: VmExecutor<F, VB::VmConfig>,
148
149 app_pk: OnceLock<AppProvingKey<VB::VmConfig>>,
150 agg_pk: OnceLock<AggProvingKey>,
153 dummy_internal_proof: OnceLock<Proof<SC>>,
154
155 #[cfg(feature = "evm-prove")]
156 #[getset(get = "pub", get_mut = "pub", set_with = "pub")]
157 halo2_params_reader: CacheHalo2ParamsReader,
158 #[cfg(feature = "evm-prove")]
159 halo2_pk: OnceLock<Halo2ProvingKey>,
160
161 #[getset(get = "pub")]
162 app_vm_builder: VB,
163 #[getset(get = "pub")]
164 native_builder: NativeBuilder,
165 transpiler: Option<Transpiler<F>>,
166
167 _phantom: PhantomData<E>,
168}
169
170pub type CpuSdk = GenericSdk<BabyBearPoseidon2Engine, SdkVmCpuBuilder, NativeCpuBuilder>;
171
172#[cfg(feature = "cuda")]
173pub type GpuSdk = GenericSdk<GpuBabyBearPoseidon2Engine, SdkVmGpuBuilder, NativeGpuBuilder>;
174
175impl<E, VB, NativeBuilder> GenericSdk<E, VB, NativeBuilder>
176where
177 E: StarkFriEngine<SC = SC>,
178 VB: VmBuilder<E, VmConfig = SdkVmConfig> + Clone + Default,
179 NativeBuilder: Clone + Default,
180{
181 #[doc = include_str!("./config/openvm_standard.toml")]
190 pub fn standard() -> Self {
192 GenericSdk::new(AppConfig::standard()).unwrap()
193 }
194
195 #[doc = include_str!("./config/openvm_riscv32.toml")]
201 pub fn riscv32() -> Self {
203 GenericSdk::new(AppConfig::riscv32()).unwrap()
204 }
205}
206
207impl<E, VB, NativeBuilder> GenericSdk<E, VB, NativeBuilder>
208where
209 E: StarkFriEngine<SC = SC>,
210 VB: VmBuilder<E>,
211{
212 pub fn new(app_config: AppConfig<VB::VmConfig>) -> Result<Self, SdkError>
214 where
215 VB: Default,
216 NativeBuilder: Default,
217 VB::VmConfig: TranspilerConfig<F>,
218 {
219 let transpiler = app_config.app_vm_config.transpiler();
220 let sdk = Self::new_without_transpiler(app_config)?.with_transpiler(transpiler);
221 Ok(sdk)
222 }
223
224 pub fn new_without_transpiler(app_config: AppConfig<VB::VmConfig>) -> Result<Self, SdkError>
227 where
228 VB: Default,
229 NativeBuilder: Default,
230 {
231 let system_config = app_config.app_vm_config.as_ref();
232 let profiling = system_config.profiling;
233 let compiler_options = CompilerOptions {
234 enable_cycle_tracker: profiling,
235 ..Default::default()
236 };
237 let executor = VmExecutor::new(app_config.app_vm_config.clone())
238 .map_err(|e| SdkError::Vm(e.into()))?;
239 let agg_config = AggregationConfig {
240 max_num_user_public_values: system_config.num_public_values,
241 leaf_fri_params: app_config.leaf_fri_params.fri_params,
242 profiling,
243 compiler_options,
244 ..Default::default()
245 };
246 #[cfg(feature = "evm-prove")]
247 let halo2_config = Halo2Config {
248 profiling,
249 ..Default::default()
250 };
251 Ok(Self {
252 app_config,
253 agg_config,
254 #[cfg(feature = "evm-prove")]
255 halo2_config,
256 agg_tree_config: Default::default(),
257 app_vm_builder: Default::default(),
258 native_builder: Default::default(),
259 transpiler: None,
260 executor,
261 app_pk: OnceLock::new(),
262 agg_pk: OnceLock::new(),
263 dummy_internal_proof: OnceLock::new(),
264 #[cfg(feature = "evm-prove")]
265 halo2_params_reader: CacheHalo2ParamsReader::new_with_default_params_dir(),
266 #[cfg(feature = "evm-prove")]
267 halo2_pk: OnceLock::new(),
268 _phantom: PhantomData,
269 })
270 }
271
272 pub fn build<P: AsRef<Path>>(
275 &self,
276 guest_opts: GuestOptions,
277 pkg_dir: P,
278 target_filter: &Option<TargetFilter>,
279 init_file_name: Option<&str>, ) -> Result<Elf, SdkError> {
281 self.app_config
282 .app_vm_config
283 .write_to_init_file(pkg_dir.as_ref(), init_file_name)?;
284 let pkg = get_package(pkg_dir.as_ref());
285 let target_dir = match build_guest_package(&pkg, &guest_opts, None, target_filter) {
286 Ok(target_dir) => target_dir,
287 Err(Some(code)) => {
288 return Err(SdkError::BuildFailedWithCode(code));
289 }
290 Err(None) => {
291 return Err(SdkError::BuildFailed);
292 }
293 };
294
295 let elf_path =
296 find_unique_executable(pkg_dir, target_dir, target_filter).map_err(SdkError::Other)?;
297 let data = read(&elf_path)?;
298 Elf::decode(&data, MEM_SIZE as u32).map_err(SdkError::Other)
299 }
300
301 pub fn transpiler(&self) -> Result<&Transpiler<F>, SdkError> {
303 self.transpiler
304 .as_ref()
305 .ok_or(SdkError::TranspilerNotAvailable)
306 }
307 pub fn set_transpiler(&mut self, transpiler: Transpiler<F>) {
308 self.transpiler = Some(transpiler);
309 }
310 pub fn with_transpiler(mut self, transpiler: Transpiler<F>) -> Self {
311 self.set_transpiler(transpiler);
312 self
313 }
314
315 pub fn convert_to_exe(
316 &self,
317 executable: impl Into<ExecutableFormat>,
318 ) -> Result<Arc<VmExe<F>>, SdkError> {
319 let executable = executable.into();
320 let exe = match executable {
321 ExecutableFormat::Elf(elf) => {
322 let transpiler = self.transpiler()?.clone();
323 Arc::new(VmExe::from_elf(elf, transpiler)?)
324 }
325 ExecutableFormat::VmExe(exe) => Arc::new(exe),
326 ExecutableFormat::SharedVmExe(exe) => exe,
327 };
328 Ok(exe)
329 }
330}
331
332impl<E, VB, NativeBuilder> GenericSdk<E, VB, NativeBuilder>
335where
336 E: StarkFriEngine<SC = SC>,
337 VB: VmBuilder<E> + Clone,
338 <VB::VmConfig as VmExecutionConfig<F>>::Executor:
339 Executor<F> + MeteredExecutor<F> + PreflightExecutor<F, VB::RecordArena>,
340 NativeBuilder: VmBuilder<E, VmConfig = NativeConfig> + Clone,
341 <NativeConfig as VmExecutionConfig<F>>::Executor:
342 PreflightExecutor<F, <NativeBuilder as VmBuilder<E>>::RecordArena>,
343{
344 pub fn execute(
346 &self,
347 app_exe: impl Into<ExecutableFormat>,
348 inputs: StdIn,
349 ) -> Result<Vec<u8>, SdkError> {
350 let exe = self.convert_to_exe(app_exe)?;
351 let instance = self
352 .executor
353 .instance(&exe)
354 .map_err(VirtualMachineError::from)?;
355 let final_memory = instance
356 .execute(inputs, None)
357 .map_err(VirtualMachineError::from)?
358 .memory;
359 let public_values = extract_public_values(
360 self.executor.config.as_ref().num_public_values,
361 &final_memory.memory,
362 );
363 Ok(public_values)
364 }
365
366 pub fn execute_metered(
369 &self,
370 app_exe: impl Into<ExecutableFormat>,
371 inputs: StdIn,
372 ) -> Result<(Vec<u8>, Vec<Segment>), SdkError> {
373 let app_prover = self.app_prover(app_exe)?;
374
375 let vm = app_prover.vm();
376 let exe = app_prover.exe();
377
378 let ctx = vm.build_metered_ctx(&exe);
379 let interpreter = vm
380 .metered_interpreter(&exe)
381 .map_err(VirtualMachineError::from)?;
382
383 let (segments, final_state) = interpreter
384 .execute_metered(inputs, ctx)
385 .map_err(VirtualMachineError::from)?;
386 let public_values = extract_public_values(
387 self.executor.config.as_ref().num_public_values,
388 &final_state.memory.memory,
389 );
390
391 Ok((public_values, segments))
392 }
393
394 pub fn execute_metered_cost(
397 &self,
398 app_exe: impl Into<ExecutableFormat>,
399 inputs: StdIn,
400 ) -> Result<(Vec<u8>, (u64, u64)), SdkError> {
401 let app_prover = self.app_prover(app_exe)?;
402
403 let vm = app_prover.vm();
404 let exe = app_prover.exe();
405
406 let ctx = vm.build_metered_cost_ctx();
407 let interpreter = vm
408 .metered_cost_interpreter(&exe)
409 .map_err(VirtualMachineError::from)?;
410
411 let (ctx, final_state) = interpreter
412 .execute_metered_cost(inputs, ctx)
413 .map_err(VirtualMachineError::from)?;
414 let instret = ctx.instret;
415 let cost = ctx.cost;
416
417 let public_values = extract_public_values(
418 self.executor.config.as_ref().num_public_values,
419 &final_state.memory.memory,
420 );
421
422 Ok((public_values, (cost, instret)))
423 }
424
425 pub fn prove(
444 &self,
445 app_exe: impl Into<ExecutableFormat>,
446 inputs: StdIn,
447 ) -> Result<(VmStarkProof<SC>, AppExecutionCommit), SdkError> {
448 let mut prover = self.prover(app_exe)?;
449 let app_commit = prover.app_prover.app_commit();
450 let proof = prover.prove(inputs)?;
451 Ok((proof, app_commit))
452 }
453
454 #[cfg(feature = "evm-prove")]
455 pub fn prove_evm(
456 &self,
457 app_exe: impl Into<ExecutableFormat>,
458 inputs: StdIn,
459 ) -> Result<EvmProof, SdkError> {
460 let app_exe = self.convert_to_exe(app_exe)?;
461 let mut evm_prover = self.evm_prover(app_exe)?;
462 let proof = evm_prover.prove_evm(inputs)?;
463 Ok(proof)
464 }
465
466 pub fn prover(
472 &self,
473 app_exe: impl Into<ExecutableFormat>,
474 ) -> Result<StarkProver<E, VB, NativeBuilder>, SdkError> {
475 let app_exe = self.convert_to_exe(app_exe)?;
476 let app_pk = self.app_pk();
477 let agg_pk = self.agg_pk();
478 let stark_prover = StarkProver::<E, _, _>::new(
479 self.app_vm_builder.clone(),
480 self.native_builder.clone(),
481 app_pk,
482 app_exe,
483 agg_pk,
484 self.agg_tree_config,
485 )?;
486 Ok(stark_prover)
487 }
488
489 #[cfg(feature = "evm-prove")]
490 pub fn evm_prover(
491 &self,
492 app_exe: impl Into<ExecutableFormat>,
493 ) -> Result<EvmHalo2Prover<E, VB, NativeBuilder>, SdkError> {
494 let app_exe = self.convert_to_exe(app_exe)?;
495 let evm_prover = EvmHalo2Prover::<E, _, _>::new(
496 self.halo2_params_reader(),
497 self.app_vm_builder.clone(),
498 self.native_builder.clone(),
499 self.app_pk(),
500 app_exe,
501 self.agg_pk(),
502 self.halo2_pk().clone(),
503 self.agg_tree_config,
504 )?;
505 Ok(evm_prover)
506 }
507
508 pub fn app_prover(
516 &self,
517 exe: impl Into<ExecutableFormat>,
518 ) -> Result<AppProver<E, VB>, SdkError> {
519 let exe = self.convert_to_exe(exe)?;
520 let app_pk = self.app_pk();
521 let prover = AppProver::<E, VB>::new(
522 self.app_vm_builder.clone(),
523 &app_pk.app_vm_pk,
524 exe,
525 app_pk.leaf_verifier_program_commit(),
526 )?;
527 Ok(prover)
528 }
529
530 pub fn app_keygen(&self) -> (AppProvingKey<VB::VmConfig>, AppVerifyingKey) {
537 let pk = self.app_pk().clone();
538 let vk = pk.get_app_vk();
539 (pk, vk)
540 }
541
542 pub fn app_pk(&self) -> &AppProvingKey<VB::VmConfig> {
547 self.app_pk.get_or_init(|| {
549 AppProvingKey::keygen(self.app_config.clone()).expect("app_keygen failed")
550 })
551 }
552 pub fn set_app_pk(
555 &self,
556 app_pk: AppProvingKey<VB::VmConfig>,
557 ) -> Result<(), AppProvingKey<VB::VmConfig>> {
558 self.app_pk.set(app_pk)
559 }
560 pub fn with_app_pk(self, app_pk: AppProvingKey<VB::VmConfig>) -> Self {
563 let _ = self
564 .set_app_pk(app_pk)
565 .map_err(|_| panic!("app_pk already set"));
566 self
567 }
568
569 pub fn agg_keygen(&self) -> Result<(AggProvingKey, AggVerifyingKey), SdkError> {
577 let agg_pk = self.agg_pk().clone();
578 let agg_vk = agg_pk.get_agg_vk();
579 Ok((agg_pk, agg_vk))
580 }
581
582 pub fn agg_pk(&self) -> &AggProvingKey {
583 self.agg_pk.get_or_init(|| {
585 let (agg_pk, dummy_proof) =
586 AggProvingKey::dummy_proof_and_keygen(self.agg_config).expect("agg_keygen failed");
587 let prev = self.dummy_internal_proof.set(dummy_proof);
588 if prev.is_err() {
589 tracing::debug!("dummy proof already exists, did not overwrite");
590 }
591 agg_pk
592 })
593 }
594 pub fn set_agg_pk(&self, agg_pk: AggProvingKey) -> Result<(), AggProvingKey> {
597 self.agg_pk.set(agg_pk)
598 }
599 pub fn with_agg_pk(self, agg_pk: AggProvingKey) -> Self {
602 let _ = self
603 .set_agg_pk(agg_pk)
604 .map_err(|_| panic!("agg_pk already set"));
605 self
606 }
607 fn dummy_internal_proof(&self) -> &Proof<SC> {
609 self.dummy_internal_proof.get_or_init(|| {
610 let (agg_pk, dummy_proof) =
611 AggProvingKey::dummy_proof_and_keygen(self.agg_config).expect("agg_keygen failed");
612 let prev = self.agg_pk.set(agg_pk);
613 if prev.is_err() {
614 tracing::debug!("agg_pk already exists, did not overwrite");
615 }
616 dummy_proof
617 })
618 }
619 pub fn agg_pk_and_dummy_internal_proof(&self) -> (&AggProvingKey, &Proof<SC>) {
620 (self.agg_pk(), self.dummy_internal_proof())
621 }
622
623 pub fn generate_root_verifier_asm(&self) -> String {
624 let agg_pk = self.agg_pk();
625 let kernel_asm = RootVmVerifierConfig {
626 leaf_fri_params: agg_pk.leaf_vm_pk.fri_params,
627 internal_fri_params: agg_pk.internal_vm_pk.fri_params,
628 num_user_public_values: agg_pk.num_user_public_values(),
629 internal_vm_verifier_commit: agg_pk.internal_committed_exe.get_program_commit().into(),
630 compiler_options: Default::default(),
631 }
632 .build_kernel_asm(
633 &agg_pk.leaf_vm_pk.vm_pk.get_vk(),
634 &agg_pk.internal_vm_pk.vm_pk.get_vk(),
635 );
636 program_to_asm(kernel_asm)
637 }
638
639 #[cfg(feature = "evm-prove")]
640 pub fn halo2_keygen(&self) -> Halo2ProvingKey {
641 self.halo2_pk().clone()
642 }
643
644 #[cfg(feature = "evm-prove")]
645 pub fn halo2_pk(&self) -> &Halo2ProvingKey {
646 let (agg_pk, dummy_internal_proof) = self.agg_pk_and_dummy_internal_proof();
647 self.halo2_pk.get_or_init(|| {
649 Halo2ProvingKey::keygen(
650 self.halo2_config,
651 self.halo2_params_reader(),
652 &DefaultStaticVerifierPvHandler,
653 agg_pk,
654 dummy_internal_proof.clone(),
655 )
656 .expect("halo2_keygen failed")
657 })
658 }
659 #[cfg(feature = "evm-prove")]
662 pub fn set_halo2_pk(&self, halo2_pk: Halo2ProvingKey) -> Result<(), Halo2ProvingKey> {
663 self.halo2_pk.set(halo2_pk)
664 }
665 #[cfg(feature = "evm-prove")]
668 pub fn with_halo2_pk(self, halo2_pk: Halo2ProvingKey) -> Self {
669 let _ = self
670 .set_halo2_pk(halo2_pk)
671 .map_err(|_| panic!("halo2_pk already set"));
672 self
673 }
674
675 #[cfg(feature = "evm-prove")]
676 pub fn with_halo2_params_dir(mut self, params_dir: impl AsRef<Path>) -> Self {
677 self.set_halo2_params_dir(params_dir);
678 self
679 }
680 #[cfg(feature = "evm-prove")]
681 pub fn set_halo2_params_dir(&mut self, params_dir: impl AsRef<Path>) {
682 self.halo2_params_reader = CacheHalo2ParamsReader::new(params_dir);
683 }
684
685 pub fn verify_proof(
692 agg_vk: &AggVerifyingKey,
693 expected_app_commit: AppExecutionCommit,
694 proof: &VmStarkProof<SC>,
695 ) -> Result<(), SdkError> {
696 if proof.inner.per_air.len() < 3 {
697 return Err(VmVerificationError::NotEnoughAirs(proof.inner.per_air.len()).into());
698 } else if proof.inner.per_air[0].air_id != PROGRAM_AIR_ID {
699 return Err(VmVerificationError::SystemAirMissing {
700 air_id: PROGRAM_AIR_ID,
701 }
702 .into());
703 } else if proof.inner.per_air[1].air_id != CONNECTOR_AIR_ID {
704 return Err(VmVerificationError::SystemAirMissing {
705 air_id: CONNECTOR_AIR_ID,
706 }
707 .into());
708 } else if proof.inner.per_air[2].air_id != PUBLIC_VALUES_AIR_ID {
709 return Err(VmVerificationError::SystemAirMissing {
710 air_id: PUBLIC_VALUES_AIR_ID,
711 }
712 .into());
713 }
714 let public_values_air_proof_data = &proof.inner.per_air[2];
715
716 let program_commit =
717 proof.inner.commitments.main_trace[PROGRAM_CACHED_TRACE_INDEX].as_ref();
718 let internal_commit: &[_; CHUNK] = &agg_vk.internal_verifier_program_commit.into();
719
720 let (fri_params_final, vk_final, claimed_app_vm_commit) =
721 if program_commit == internal_commit {
722 let internal_pvs: &InternalVmVerifierPvs<_> = public_values_air_proof_data
723 .public_values
724 .as_slice()
725 .borrow();
726 if internal_commit != &internal_pvs.extra_pvs.internal_program_commit {
727 tracing::debug!(
728 "Invalid internal program commit: expected {:?}, got {:?}",
729 internal_commit,
730 internal_pvs.extra_pvs.internal_program_commit
731 );
732 return Err(VmVerificationError::ProgramCommitMismatch { index: 0 }.into());
733 }
734 (
735 agg_vk.internal_fri_params,
736 &agg_vk.internal_vk,
737 internal_pvs.extra_pvs.leaf_verifier_commit,
738 )
739 } else {
740 (agg_vk.leaf_fri_params, &agg_vk.leaf_vk, *program_commit)
741 };
742 let e = E::new(fri_params_final);
743 e.verify(vk_final, &proof.inner)
744 .map_err(VmVerificationError::from)?;
745
746 let pvs: &VmVerifierPvs<_> =
747 public_values_air_proof_data.public_values[..VmVerifierPvs::<u8>::width()].borrow();
748
749 if let Some(exit_code) = pvs.connector.exit_code() {
750 if exit_code != 0 {
751 return Err(VmVerificationError::ExitCodeMismatch {
752 expected: 0,
753 actual: exit_code,
754 }
755 .into());
756 }
757 } else {
758 return Err(VmVerificationError::IsTerminateMismatch {
759 expected: true,
760 actual: false,
761 }
762 .into());
763 }
764
765 let hasher = vm_poseidon2_hasher();
766 let public_values_root = hasher.merkle_root(&proof.user_public_values);
767 if public_values_root != pvs.public_values_commit {
768 tracing::debug!(
769 "Invalid public values root: expected {:?}, got {:?}",
770 pvs.public_values_commit,
771 public_values_root
772 );
773 return Err(VmVerificationError::UserPublicValuesError(
774 UserPublicValuesProofError::UserPublicValuesCommitMismatch,
775 )
776 .into());
777 }
778
779 let claimed_app_exe_commit = compute_exe_commit(
780 &hasher,
781 &pvs.app_commit,
782 &pvs.memory.initial_root,
783 pvs.connector.initial_pc,
784 );
785 let claimed_app_commit =
786 AppExecutionCommit::from_field_commit(claimed_app_exe_commit, claimed_app_vm_commit);
787 let exe_commit_bn254 = claimed_app_commit.app_exe_commit.to_bn254();
788 let vm_commit_bn254 = claimed_app_commit.app_vm_commit.to_bn254();
789
790 if exe_commit_bn254 != expected_app_commit.app_exe_commit.to_bn254() {
791 return Err(SdkError::InvalidAppExeCommit {
792 expected: expected_app_commit.app_exe_commit,
793 actual: claimed_app_commit.app_exe_commit,
794 });
795 } else if vm_commit_bn254 != expected_app_commit.app_vm_commit.to_bn254() {
796 return Err(SdkError::InvalidAppVmCommit {
797 expected: expected_app_commit.app_vm_commit,
798 actual: claimed_app_commit.app_vm_commit,
799 });
800 }
801 Ok(())
802 }
803
804 #[cfg(feature = "evm-verify")]
805 pub fn generate_halo2_verifier_solidity(&self) -> Result<types::EvmHalo2Verifier, SdkError> {
806 use std::{
807 fs::{create_dir_all, write},
808 io::Write,
809 process::{Command, Stdio},
810 };
811
812 use eyre::Context;
813 use forge_fmt::{
814 format, FormatterConfig, IntTypes, MultilineFuncHeaderStyle, NumberUnderscore,
815 QuoteStyle, SingleLineBlockStyle,
816 };
817 use openvm_native_recursion::halo2::wrapper::EvmVerifierByteCode;
818 use serde_json::{json, Value};
819 use snark_verifier::halo2_base::halo2_proofs::poly::commitment::Params;
820 use snark_verifier_sdk::SHPLONK;
821 use tempfile::tempdir;
822 use types::EvmHalo2Verifier;
823
824 use crate::fs::{
825 EVM_HALO2_VERIFIER_BASE_NAME, EVM_HALO2_VERIFIER_INTERFACE_NAME,
826 EVM_HALO2_VERIFIER_PARENT_NAME,
827 };
828
829 let reader = self.halo2_params_reader();
830 let halo2_pk = self.halo2_pk();
831
832 let params = reader.read_params(halo2_pk.wrapper.pinning.metadata.config_params.k);
833 let pinning = &halo2_pk.wrapper.pinning;
834
835 assert_eq!(
836 pinning.metadata.config_params.k as u32,
837 params.k(),
838 "Provided params don't match circuit config"
839 );
840
841 let halo2_verifier_code = gen_evm_verifier_sol_code::<AggregationCircuit, SHPLONK>(
842 ¶ms,
843 pinning.pk.get_vk(),
844 pinning.metadata.num_pvs.clone(),
845 );
846
847 let wrapper_pvs = halo2_pk.wrapper.pinning.metadata.num_pvs.clone();
848 let pvs_length = match wrapper_pvs.first() {
849 Some(v) => v
852 .checked_sub(14)
853 .expect("Unexpected number of static verifier wrapper public values"),
854 _ => panic!("Unexpected amount of instance columns in the static verifier wrapper"),
855 };
856
857 assert!(
858 pvs_length <= 8192,
859 "OpenVM Halo2 verifier contract does not support more than 8192 public values"
860 );
861
862 let openvm_verifier_code = EVM_HALO2_VERIFIER_TEMPLATE
864 .replace("{PUBLIC_VALUES_LENGTH}", &pvs_length.to_string())
865 .replace("{OPENVM_VERSION}", OPENVM_VERSION);
866
867 let formatter_config = FormatterConfig {
868 line_length: 120,
869 tab_width: 4,
870 bracket_spacing: true,
871 int_types: IntTypes::Long,
872 multiline_func_header: MultilineFuncHeaderStyle::AttributesFirst,
873 quote_style: QuoteStyle::Double,
874 number_underscore: NumberUnderscore::Thousands,
875 single_line_statement_blocks: SingleLineBlockStyle::Preserve,
876 override_spacing: false,
877 wrap_comments: false,
878 ignore: vec![],
879 contract_new_lines: false,
880 sort_imports: false,
881 ..Default::default()
882 };
883
884 let formatted_interface = format(EVM_HALO2_VERIFIER_INTERFACE, formatter_config.clone())
885 .into_result()
886 .expect("Failed to format interface");
887 let formatted_halo2_verifier_code = format(&halo2_verifier_code, formatter_config.clone())
888 .into_result()
889 .expect("Failed to format halo2 verifier code");
890 let formatted_openvm_verifier_code = format(&openvm_verifier_code, formatter_config)
891 .into_result()
892 .expect("Failed to format openvm verifier code");
893
894 let temp_dir = tempdir()
896 .wrap_err("Failed to create temp dir")
897 .map_err(SdkError::Other)?;
898 let temp_path = temp_dir.path();
899 let root_path = Path::new("src").join(format!("v{OPENVM_VERSION}"));
900
901 let interfaces_path = root_path.join("interfaces");
903
904 create_dir_all(temp_path.join(&interfaces_path))?;
907
908 let interface_file_path = interfaces_path.join(EVM_HALO2_VERIFIER_INTERFACE_NAME);
909 let parent_file_path = root_path.join(EVM_HALO2_VERIFIER_PARENT_NAME);
910 let base_file_path = root_path.join(EVM_HALO2_VERIFIER_BASE_NAME);
911
912 write(temp_path.join(&interface_file_path), &formatted_interface)?;
915 write(
916 temp_path.join(&parent_file_path),
917 &formatted_halo2_verifier_code,
918 )?;
919 write(
920 temp_path.join(&base_file_path),
921 &formatted_openvm_verifier_code,
922 )?;
923
924 let solc_input = json!({
926 "language": "Solidity",
927 "sources": {
928 interface_file_path.to_str().unwrap(): {
929 "content": formatted_interface
930 },
931 parent_file_path.to_str().unwrap(): {
932 "content": formatted_halo2_verifier_code
933 },
934 base_file_path.to_str().unwrap(): {
935 "content": formatted_openvm_verifier_code
936 }
937 },
938 "settings": {
939 "remappings": ["forge-std/=lib/forge-std/src/"],
940 "optimizer": {
941 "enabled": true,
942 "runs": 100000,
943 "details": {
944 "constantOptimizer": false,
945 "yul": false
946 }
947 },
948 "evmVersion": "paris",
949 "viaIR": false,
950 "outputSelection": {
951 "*": {
952 "*": ["metadata", "evm.bytecode.object"]
953 }
954 }
955 }
956 });
957
958 let mut child = Command::new("solc")
959 .current_dir(temp_path)
960 .arg("--standard-json")
961 .stdin(Stdio::piped())
962 .stdout(Stdio::piped())
963 .stderr(Stdio::piped())
964 .spawn()
965 .expect("Failed to spawn solc");
966
967 child
968 .stdin
969 .as_mut()
970 .expect("Failed to open stdin")
971 .write_all(solc_input.to_string().as_bytes())
972 .expect("Failed to write to stdin");
973
974 let output = child.wait_with_output().expect("Failed to read output");
975
976 if !output.status.success() {
977 return Err(SdkError::Other(eyre::eyre!(
978 "solc exited with status {}: {}",
979 output.status,
980 String::from_utf8_lossy(&output.stderr)
981 )));
982 }
983
984 let parsed: Value =
985 serde_json::from_slice(&output.stdout).map_err(|e| SdkError::Other(e.into()))?;
986
987 let bytecode = parsed
988 .get("contracts")
989 .expect("No 'contracts' field found")
990 .get(format!("src/v{OPENVM_VERSION}/OpenVmHalo2Verifier.sol"))
991 .unwrap_or_else(|| {
992 panic!("No 'src/v{OPENVM_VERSION}/OpenVmHalo2Verifier.sol' field found")
993 })
994 .get("OpenVmHalo2Verifier")
995 .expect("No 'OpenVmHalo2Verifier' field found")
996 .get("evm")
997 .expect("No 'evm' field found")
998 .get("bytecode")
999 .expect("No 'bytecode' field found")
1000 .get("object")
1001 .expect("No 'object' field found")
1002 .as_str()
1003 .expect("No 'object' field found");
1004
1005 let bytecode = hex::decode(bytecode).expect("Invalid hex in Binary");
1006
1007 let evm_verifier = EvmHalo2Verifier {
1008 halo2_verifier_code: formatted_halo2_verifier_code,
1009 openvm_verifier_code: formatted_openvm_verifier_code,
1010 openvm_verifier_interface: formatted_interface,
1011 artifact: EvmVerifierByteCode {
1012 sol_compiler_version: "0.8.19".to_string(),
1013 sol_compiler_options: solc_input.get("settings").unwrap().to_string(),
1014 bytecode,
1015 },
1016 };
1017 Ok(evm_verifier)
1018 }
1019
1020 #[cfg(feature = "evm-verify")]
1021 pub fn verify_evm_halo2_proof(
1023 openvm_verifier: &types::EvmHalo2Verifier,
1024 evm_proof: EvmProof,
1025 ) -> Result<u64, SdkError> {
1026 let calldata = evm_proof.verifier_calldata();
1027 let deployment_code = openvm_verifier.artifact.bytecode.clone();
1028
1029 let gas_cost = snark_verifier::loader::evm::deploy_and_call(deployment_code, calldata)
1030 .map_err(|reason| {
1031 SdkError::Other(eyre::eyre!("Sdk::verify_openvm_evm_proof: {reason:?}"))
1032 })?;
1033
1034 Ok(gas_cost)
1035 }
1036}