openvm_sdk/
lib.rs

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};
44// Re-exports:
45pub 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// The SDK is only generic in the engine for the non-root SC. The root SC is fixed to
116// BabyBearPoseidon2RootEngine right now.
117/// The SDK provides convenience methods and constructors for provers.
118///
119/// The SDK is stateful to cache results of computations that depend only on the App VM config and
120/// aggregation config. The SDK will not cache any state that depends on the program executable.
121///
122/// Some commonly used methods are:
123/// - [`execute`](Self::execute)
124/// - [`prove`](Self::prove)
125/// - [`verify_proof`](Self::verify_proof)
126#[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    /// The `executor` may be used to construct different types of interpreters, given the program,
144    /// for more specific execution purposes. By default, it is recommended to use the
145    /// [`execute`](GenericSdk::execute) method.
146    #[getset(get = "pub")]
147    executor: VmExecutor<F, VB::VmConfig>,
148
149    app_pk: OnceLock<AppProvingKey<VB::VmConfig>>,
150    /// STARK aggregation proving key and dummy internal proof. Dummy internal proof is saved for
151    /// halo2 pkey generation usage.
152    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    /// Creates SDK with a standard configuration that includes a set of default VM extensions
182    /// loaded.
183    ///
184    /// **Note**: To use this configuration, your `openvm.toml` must match, including the order of
185    /// the moduli and elliptic curve parameters of the respective extensions:
186    /// The `app_vm_config` field of your `openvm.toml` must exactly match the following:
187    ///
188    /// ```toml
189    #[doc = include_str!("./config/openvm_standard.toml")]
190    /// ```
191    pub fn standard() -> Self {
192        GenericSdk::new(AppConfig::standard()).unwrap()
193    }
194
195    /// Creates SDK with a configuration with RISC-V RV32IM and IO VM extensions loaded.
196    ///
197    /// **Note**: To use this configuration, your `openvm.toml` must exactly match the following:
198    ///
199    /// ```toml
200    #[doc = include_str!("./config/openvm_riscv32.toml")]
201    /// ```
202    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    /// Creates SDK custom to the given [AppConfig], with a RISC-V transpiler.
213    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    /// **Note**: This function does not set the transpiler, which must be done separately to
225    /// support RISC-V ELFs.
226    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    /// Builds the guest package located at `pkg_dir`. This function requires that the build target
273    /// is unique and errors otherwise. Returns the built ELF file decoded in the [Elf] type.
274    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>, // If None, we use "openvm-init.rs"
280    ) -> 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    /// Transpiler for transpiling RISC-V ELF to OpenVM executable.
302    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
332// The SDK is only functional for SC = BabyBearPoseidon2Config because that is what recursive
333// aggregation supports.
334impl<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    /// Returns the user public values as field elements.
345    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    /// Executes with segmentation for proof generation.
367    /// Returns both user public values and segments with instruction counts and trace heights.
368    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    /// Executes with cost metering to measure computational cost in trace cells.
395    /// Returns both user public values, and cost along with instruction count.
396    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    // ======================== Proving Methods ============================
426
427    /// Generates a single aggregate STARK proof of the full program execution of the given
428    /// `app_exe` with program inputs `inputs`.
429    ///
430    /// The returned STARK proof is not intended for EVM verification. For EVM verification, use the
431    /// `prove_evm` method, which requires the `"evm-prove"` feature to be
432    /// enabled.
433    ///
434    /// For convenience, this function also returns the [AppExecutionCommit], which is a full
435    /// commitment to the App VM config and the App [VmExe]. It does **not** depend on the `inputs`.
436    /// It can be generated separately from the proof by creating a
437    /// [`prover`](Self::prover) and calling
438    /// [`app_commit`](StarkProver::app_commit).
439    ///
440    /// If STARK aggregation is not needed and a proof whose size may grow linearly with the length
441    /// of the program runtime is desired, create an [`app_prover`](Self::app_prover) and call
442    /// [`app_prover.prove(inputs)`](AppProver::prove).
443    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    // ========================= Prover Constructors =========================
467
468    /// Constructs a new [StarkProver] instance for the given executable.
469    /// This function will generate the [AppProvingKey] and [AggProvingKey] if they do not already
470    /// exist.
471    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    /// This constructor is for generating app proofs that do not require a single aggregate STARK
509    /// proof of the full program execution. For a single STARK proof, use the
510    /// [`prove`](Self::prove) method instead.
511    ///
512    /// Creates an app prover instance specific to the provided exe.
513    /// This function will generate the [AppProvingKey] if it doesn't already exist and use it to
514    /// construct the [AppProver].
515    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    // ======================== Keygen Related Methods ========================
531
532    /// Generates the app proving key once and caches it. Future calls will return the cached key.
533    ///
534    /// # Panics
535    /// This function will panic if the app keygen fails.
536    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    /// Generates the app proving key once and caches it. Future calls will return the cached key.
543    ///
544    /// # Panics
545    /// This function will panic if the app keygen fails.
546    pub fn app_pk(&self) -> &AppProvingKey<VB::VmConfig> {
547        // TODO[jpw]: use `get_or_try_init` once it is stable
548        self.app_pk.get_or_init(|| {
549            AppProvingKey::keygen(self.app_config.clone()).expect("app_keygen failed")
550        })
551    }
552    /// Sets the app proving key. Returns `Ok(())` if app keygen has not been called and
553    /// `Err(app_pk)` if keygen has already been called.
554    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    /// See [`set_app_pk`](Self::set_app_pk). This should only be used in a constructor, and panics
561    /// if app keygen has already been called.
562    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    /// Generates the proving keys necessary for STARK aggregation. Generates the proving keys once
570    /// and caches them. Future calls will return the cached key. This function does not include
571    /// [`app_keygen`](Self::app_keygen), which is specific to the App VM config. The proving keys
572    /// generated in this step are independent of the App VM config.
573    ///
574    /// # Panics
575    /// This function will panic if the keygen fails.
576    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        // TODO[jpw]: use `get_or_try_init` once it is stable
584        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    /// Sets the aggregation proving keys. Returns `Ok(())` if agg keygen has not been called and
595    /// `Err(agg_pk)` if keygen has already been called.
596    pub fn set_agg_pk(&self, agg_pk: AggProvingKey) -> Result<(), AggProvingKey> {
597        self.agg_pk.set(agg_pk)
598    }
599    /// See [`set_agg_pk`](Self::set_agg_pk). This should only be used in a constructor, and panics
600    /// if app keygen has already been called.
601    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    // We have this function in case agg_pk is set externally without setting dummy proof.
608    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        // TODO[jpw]: use `get_or_try_init` once it is stable
648        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    /// Sets the halo2 proving keys. Returns `Ok(())` if halo2 keygen has not been called and
660    /// `Err(halo2_pk)` if keygen has already been called.
661    #[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    /// See [`set_halo2_pk`](Self::set_halo2_pk). This should only be used in a constructor, and
666    /// panics if halo2 keygen has already been called.
667    #[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    // ======================== Verification Methods ========================
686
687    /// Verifies aggregate STARK proof of VM execution.
688    ///
689    /// **Note**: This function does not have any reliance on `self` and does not depend on the app
690    /// config set in the [Sdk].
691    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            &params,
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            // We subtract 14 to exclude the KZG accumulator and the app exe
850            // and vm commits.
851            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        // Fill out the public values length and OpenVM version in the template
863        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        // Create temp dir
895        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        // Make interfaces dir
902        let interfaces_path = root_path.join("interfaces");
903
904        // This will also create the dir for root_path, so no need to explicitly
905        // create it
906        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 the files to the temp dir. This is only for compilation
913        // purposes.
914        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        // Run solc from the temp dir
925        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    /// Uses the `verify(..)` interface of the `OpenVmHalo2Verifier` contract.
1022    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}