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();
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 (cost, final_state) = interpreter
412            .execute_metered_cost(inputs, ctx)
413            .map_err(VirtualMachineError::from)?;
414        let instret = final_state.instret;
415
416        let public_values = extract_public_values(
417            self.executor.config.as_ref().num_public_values,
418            &final_state.memory.memory,
419        );
420
421        Ok((public_values, (cost, instret)))
422    }
423
424    // ======================== Proving Methods ============================
425
426    /// Generates a single aggregate STARK proof of the full program execution of the given
427    /// `app_exe` with program inputs `inputs`.
428    ///
429    /// The returned STARK proof is not intended for EVM verification. For EVM verification, use the
430    /// `prove_evm` method, which requires the `"evm-prove"` feature to be
431    /// enabled.
432    ///
433    /// For convenience, this function also returns the [AppExecutionCommit], which is a full
434    /// commitment to the App VM config and the App [VmExe]. It does **not** depend on the `inputs`.
435    /// It can be generated separately from the proof by creating a
436    /// [`prover`](Self::prover) and calling
437    /// [`app_commit`](StarkProver::app_commit).
438    ///
439    /// If STARK aggregation is not needed and a proof whose size may grow linearly with the length
440    /// of the program runtime is desired, create an [`app_prover`](Self::app_prover) and call
441    /// [`app_prover.prove(inputs)`](AppProver::prove).
442    pub fn prove(
443        &self,
444        app_exe: impl Into<ExecutableFormat>,
445        inputs: StdIn,
446    ) -> Result<(VmStarkProof<SC>, AppExecutionCommit), SdkError> {
447        let mut prover = self.prover(app_exe)?;
448        let app_commit = prover.app_prover.app_commit();
449        let proof = prover.prove(inputs)?;
450        Ok((proof, app_commit))
451    }
452
453    #[cfg(feature = "evm-prove")]
454    pub fn prove_evm(
455        &self,
456        app_exe: impl Into<ExecutableFormat>,
457        inputs: StdIn,
458    ) -> Result<EvmProof, SdkError> {
459        let app_exe = self.convert_to_exe(app_exe)?;
460        let mut evm_prover = self.evm_prover(app_exe)?;
461        let proof = evm_prover.prove_evm(inputs)?;
462        Ok(proof)
463    }
464
465    // ========================= Prover Constructors =========================
466
467    /// Constructs a new [StarkProver] instance for the given executable.
468    /// This function will generate the [AppProvingKey] and [AggProvingKey] if they do not already
469    /// exist.
470    pub fn prover(
471        &self,
472        app_exe: impl Into<ExecutableFormat>,
473    ) -> Result<StarkProver<E, VB, NativeBuilder>, SdkError> {
474        let app_exe = self.convert_to_exe(app_exe)?;
475        let app_pk = self.app_pk();
476        let agg_pk = self.agg_pk();
477        let stark_prover = StarkProver::<E, _, _>::new(
478            self.app_vm_builder.clone(),
479            self.native_builder.clone(),
480            app_pk,
481            app_exe,
482            agg_pk,
483            self.agg_tree_config,
484        )?;
485        Ok(stark_prover)
486    }
487
488    #[cfg(feature = "evm-prove")]
489    pub fn evm_prover(
490        &self,
491        app_exe: impl Into<ExecutableFormat>,
492    ) -> Result<EvmHalo2Prover<E, VB, NativeBuilder>, SdkError> {
493        let app_exe = self.convert_to_exe(app_exe)?;
494        let evm_prover = EvmHalo2Prover::<E, _, _>::new(
495            self.halo2_params_reader(),
496            self.app_vm_builder.clone(),
497            self.native_builder.clone(),
498            self.app_pk(),
499            app_exe,
500            self.agg_pk(),
501            self.halo2_pk().clone(),
502            self.agg_tree_config,
503        )?;
504        Ok(evm_prover)
505    }
506
507    /// This constructor is for generating app proofs that do not require a single aggregate STARK
508    /// proof of the full program execution. For a single STARK proof, use the
509    /// [`prove`](Self::prove) method instead.
510    ///
511    /// Creates an app prover instance specific to the provided exe.
512    /// This function will generate the [AppProvingKey] if it doesn't already exist and use it to
513    /// construct the [AppProver].
514    pub fn app_prover(
515        &self,
516        exe: impl Into<ExecutableFormat>,
517    ) -> Result<AppProver<E, VB>, SdkError> {
518        let exe = self.convert_to_exe(exe)?;
519        let app_pk = self.app_pk();
520        let prover = AppProver::<E, VB>::new(
521            self.app_vm_builder.clone(),
522            &app_pk.app_vm_pk,
523            exe,
524            app_pk.leaf_verifier_program_commit(),
525        )?;
526        Ok(prover)
527    }
528
529    // ======================== Keygen Related Methods ========================
530
531    /// Generates the app proving key once and caches it. Future calls will return the cached key.
532    ///
533    /// # Panics
534    /// This function will panic if the app keygen fails.
535    pub fn app_keygen(&self) -> (AppProvingKey<VB::VmConfig>, AppVerifyingKey) {
536        let pk = self.app_pk().clone();
537        let vk = pk.get_app_vk();
538        (pk, vk)
539    }
540
541    /// Generates the app proving key once and caches it. Future calls will return the cached key.
542    ///
543    /// # Panics
544    /// This function will panic if the app keygen fails.
545    pub fn app_pk(&self) -> &AppProvingKey<VB::VmConfig> {
546        // TODO[jpw]: use `get_or_try_init` once it is stable
547        self.app_pk.get_or_init(|| {
548            AppProvingKey::keygen(self.app_config.clone()).expect("app_keygen failed")
549        })
550    }
551    /// Sets the app proving key. Returns `Ok(())` if app keygen has not been called and
552    /// `Err(app_pk)` if keygen has already been called.
553    pub fn set_app_pk(
554        &self,
555        app_pk: AppProvingKey<VB::VmConfig>,
556    ) -> Result<(), AppProvingKey<VB::VmConfig>> {
557        self.app_pk.set(app_pk)
558    }
559    /// See [`set_app_pk`](Self::set_app_pk). This should only be used in a constructor, and panics
560    /// if app keygen has already been called.
561    pub fn with_app_pk(self, app_pk: AppProvingKey<VB::VmConfig>) -> Self {
562        let _ = self
563            .set_app_pk(app_pk)
564            .map_err(|_| panic!("app_pk already set"));
565        self
566    }
567
568    /// Generates the proving keys necessary for STARK aggregation. Generates the proving keys once
569    /// and caches them. Future calls will return the cached key. This function does not include
570    /// [`app_keygen`](Self::app_keygen), which is specific to the App VM config. The proving keys
571    /// generated in this step are independent of the App VM config.
572    ///
573    /// # Panics
574    /// This function will panic if the keygen fails.
575    pub fn agg_keygen(&self) -> Result<(AggProvingKey, AggVerifyingKey), SdkError> {
576        let agg_pk = self.agg_pk().clone();
577        let agg_vk = agg_pk.get_agg_vk();
578        Ok((agg_pk, agg_vk))
579    }
580
581    pub fn agg_pk(&self) -> &AggProvingKey {
582        // TODO[jpw]: use `get_or_try_init` once it is stable
583        self.agg_pk.get_or_init(|| {
584            let (agg_pk, dummy_proof) =
585                AggProvingKey::dummy_proof_and_keygen(self.agg_config).expect("agg_keygen failed");
586            let prev = self.dummy_internal_proof.set(dummy_proof);
587            if prev.is_err() {
588                tracing::debug!("dummy proof already exists, did not overwrite");
589            }
590            agg_pk
591        })
592    }
593    /// Sets the aggregation proving keys. Returns `Ok(())` if agg keygen has not been called and
594    /// `Err(agg_pk)` if keygen has already been called.
595    pub fn set_agg_pk(&self, agg_pk: AggProvingKey) -> Result<(), AggProvingKey> {
596        self.agg_pk.set(agg_pk)
597    }
598    /// See [`set_agg_pk`](Self::set_agg_pk). This should only be used in a constructor, and panics
599    /// if app keygen has already been called.
600    pub fn with_agg_pk(self, agg_pk: AggProvingKey) -> Self {
601        let _ = self
602            .set_agg_pk(agg_pk)
603            .map_err(|_| panic!("agg_pk already set"));
604        self
605    }
606    // We have this function in case agg_pk is set externally without setting dummy proof.
607    fn dummy_internal_proof(&self) -> &Proof<SC> {
608        self.dummy_internal_proof.get_or_init(|| {
609            let (agg_pk, dummy_proof) =
610                AggProvingKey::dummy_proof_and_keygen(self.agg_config).expect("agg_keygen failed");
611            let prev = self.agg_pk.set(agg_pk);
612            if prev.is_err() {
613                tracing::debug!("agg_pk already exists, did not overwrite");
614            }
615            dummy_proof
616        })
617    }
618    pub fn agg_pk_and_dummy_internal_proof(&self) -> (&AggProvingKey, &Proof<SC>) {
619        (self.agg_pk(), self.dummy_internal_proof())
620    }
621
622    pub fn generate_root_verifier_asm(&self) -> String {
623        let agg_pk = self.agg_pk();
624        let kernel_asm = RootVmVerifierConfig {
625            leaf_fri_params: agg_pk.leaf_vm_pk.fri_params,
626            internal_fri_params: agg_pk.internal_vm_pk.fri_params,
627            num_user_public_values: agg_pk.num_user_public_values(),
628            internal_vm_verifier_commit: agg_pk.internal_committed_exe.get_program_commit().into(),
629            compiler_options: Default::default(),
630        }
631        .build_kernel_asm(
632            &agg_pk.leaf_vm_pk.vm_pk.get_vk(),
633            &agg_pk.internal_vm_pk.vm_pk.get_vk(),
634        );
635        program_to_asm(kernel_asm)
636    }
637
638    #[cfg(feature = "evm-prove")]
639    pub fn halo2_keygen(&self) -> Halo2ProvingKey {
640        self.halo2_pk().clone()
641    }
642
643    #[cfg(feature = "evm-prove")]
644    pub fn halo2_pk(&self) -> &Halo2ProvingKey {
645        let (agg_pk, dummy_internal_proof) = self.agg_pk_and_dummy_internal_proof();
646        // TODO[jpw]: use `get_or_try_init` once it is stable
647        self.halo2_pk.get_or_init(|| {
648            Halo2ProvingKey::keygen(
649                self.halo2_config,
650                self.halo2_params_reader(),
651                &DefaultStaticVerifierPvHandler,
652                agg_pk,
653                dummy_internal_proof.clone(),
654            )
655            .expect("halo2_keygen failed")
656        })
657    }
658    /// Sets the halo2 proving keys. Returns `Ok(())` if halo2 keygen has not been called and
659    /// `Err(halo2_pk)` if keygen has already been called.
660    #[cfg(feature = "evm-prove")]
661    pub fn set_halo2_pk(&self, halo2_pk: Halo2ProvingKey) -> Result<(), Halo2ProvingKey> {
662        self.halo2_pk.set(halo2_pk)
663    }
664    /// See [`set_halo2_pk`](Self::set_halo2_pk). This should only be used in a constructor, and
665    /// panics if halo2 keygen has already been called.
666    #[cfg(feature = "evm-prove")]
667    pub fn with_halo2_pk(self, halo2_pk: Halo2ProvingKey) -> Self {
668        let _ = self
669            .set_halo2_pk(halo2_pk)
670            .map_err(|_| "halo2_pk already set");
671        self
672    }
673
674    #[cfg(feature = "evm-prove")]
675    pub fn with_halo2_params_dir(mut self, params_dir: impl AsRef<Path>) -> Self {
676        self.set_halo2_params_dir(params_dir);
677        self
678    }
679    #[cfg(feature = "evm-prove")]
680    pub fn set_halo2_params_dir(&mut self, params_dir: impl AsRef<Path>) {
681        self.halo2_params_reader = CacheHalo2ParamsReader::new(params_dir);
682    }
683
684    // ======================== Verification Methods ========================
685
686    /// Verifies aggregate STARK proof of VM execution.
687    ///
688    /// **Note**: This function does not have any reliance on `self` and does not depend on the app
689    /// config set in the [Sdk].
690    pub fn verify_proof(
691        agg_vk: &AggVerifyingKey,
692        expected_app_commit: AppExecutionCommit,
693        proof: &VmStarkProof<SC>,
694    ) -> Result<(), SdkError> {
695        if proof.inner.per_air.len() < 3 {
696            return Err(VmVerificationError::NotEnoughAirs(proof.inner.per_air.len()).into());
697        } else if proof.inner.per_air[0].air_id != PROGRAM_AIR_ID {
698            return Err(VmVerificationError::SystemAirMissing {
699                air_id: PROGRAM_AIR_ID,
700            }
701            .into());
702        } else if proof.inner.per_air[1].air_id != CONNECTOR_AIR_ID {
703            return Err(VmVerificationError::SystemAirMissing {
704                air_id: CONNECTOR_AIR_ID,
705            }
706            .into());
707        } else if proof.inner.per_air[2].air_id != PUBLIC_VALUES_AIR_ID {
708            return Err(VmVerificationError::SystemAirMissing {
709                air_id: PUBLIC_VALUES_AIR_ID,
710            }
711            .into());
712        }
713        let public_values_air_proof_data = &proof.inner.per_air[2];
714
715        let program_commit =
716            proof.inner.commitments.main_trace[PROGRAM_CACHED_TRACE_INDEX].as_ref();
717        let internal_commit: &[_; CHUNK] = &agg_vk.internal_verifier_program_commit.into();
718
719        let (fri_params_final, vk_final, claimed_app_vm_commit) =
720            if program_commit == internal_commit {
721                let internal_pvs: &InternalVmVerifierPvs<_> = public_values_air_proof_data
722                    .public_values
723                    .as_slice()
724                    .borrow();
725                if internal_commit != &internal_pvs.extra_pvs.internal_program_commit {
726                    tracing::debug!(
727                        "Invalid internal program commit: expected {:?}, got {:?}",
728                        internal_commit,
729                        internal_pvs.extra_pvs.internal_program_commit
730                    );
731                    return Err(VmVerificationError::ProgramCommitMismatch { index: 0 }.into());
732                }
733                (
734                    agg_vk.internal_fri_params,
735                    &agg_vk.internal_vk,
736                    internal_pvs.extra_pvs.leaf_verifier_commit,
737                )
738            } else {
739                (agg_vk.leaf_fri_params, &agg_vk.leaf_vk, *program_commit)
740            };
741        let e = E::new(fri_params_final);
742        e.verify(vk_final, &proof.inner)
743            .map_err(VmVerificationError::from)?;
744
745        let pvs: &VmVerifierPvs<_> =
746            public_values_air_proof_data.public_values[..VmVerifierPvs::<u8>::width()].borrow();
747
748        if let Some(exit_code) = pvs.connector.exit_code() {
749            if exit_code != 0 {
750                return Err(VmVerificationError::ExitCodeMismatch {
751                    expected: 0,
752                    actual: exit_code,
753                }
754                .into());
755            }
756        } else {
757            return Err(VmVerificationError::IsTerminateMismatch {
758                expected: true,
759                actual: false,
760            }
761            .into());
762        }
763
764        let hasher = vm_poseidon2_hasher();
765        let public_values_root = hasher.merkle_root(&proof.user_public_values);
766        if public_values_root != pvs.public_values_commit {
767            tracing::debug!(
768                "Invalid public values root: expected {:?}, got {:?}",
769                pvs.public_values_commit,
770                public_values_root
771            );
772            return Err(VmVerificationError::UserPublicValuesError(
773                UserPublicValuesProofError::UserPublicValuesCommitMismatch,
774            )
775            .into());
776        }
777
778        let claimed_app_exe_commit = compute_exe_commit(
779            &hasher,
780            &pvs.app_commit,
781            &pvs.memory.initial_root,
782            pvs.connector.initial_pc,
783        );
784        let claimed_app_commit =
785            AppExecutionCommit::from_field_commit(claimed_app_exe_commit, claimed_app_vm_commit);
786        let exe_commit_bn254 = claimed_app_commit.app_exe_commit.to_bn254();
787        let vm_commit_bn254 = claimed_app_commit.app_vm_commit.to_bn254();
788
789        if exe_commit_bn254 != expected_app_commit.app_exe_commit.to_bn254() {
790            return Err(SdkError::InvalidAppExeCommit {
791                expected: expected_app_commit.app_exe_commit,
792                actual: claimed_app_commit.app_exe_commit,
793            });
794        } else if vm_commit_bn254 != expected_app_commit.app_vm_commit.to_bn254() {
795            return Err(SdkError::InvalidAppVmCommit {
796                expected: expected_app_commit.app_vm_commit,
797                actual: claimed_app_commit.app_vm_commit,
798            });
799        }
800        Ok(())
801    }
802
803    #[cfg(feature = "evm-verify")]
804    pub fn generate_halo2_verifier_solidity(&self) -> Result<types::EvmHalo2Verifier, SdkError> {
805        use std::{
806            fs::{create_dir_all, write},
807            io::Write,
808            process::{Command, Stdio},
809        };
810
811        use eyre::Context;
812        use forge_fmt::{
813            format, parse, FormatterConfig, IntTypes, MultilineFuncHeaderStyle, NumberUnderscore,
814            QuoteStyle, SingleLineBlockStyle,
815        };
816        use openvm_native_recursion::halo2::wrapper::EvmVerifierByteCode;
817        use serde_json::{json, Value};
818        use snark_verifier::halo2_base::halo2_proofs::poly::commitment::Params;
819        use snark_verifier_sdk::SHPLONK;
820        use tempfile::tempdir;
821        use types::EvmHalo2Verifier;
822
823        use crate::fs::{
824            EVM_HALO2_VERIFIER_BASE_NAME, EVM_HALO2_VERIFIER_INTERFACE_NAME,
825            EVM_HALO2_VERIFIER_PARENT_NAME,
826        };
827
828        let reader = self.halo2_params_reader();
829        let halo2_pk = self.halo2_pk();
830
831        let params = reader.read_params(halo2_pk.wrapper.pinning.metadata.config_params.k);
832        let pinning = &halo2_pk.wrapper.pinning;
833
834        assert_eq!(
835            pinning.metadata.config_params.k as u32,
836            params.k(),
837            "Provided params don't match circuit config"
838        );
839
840        let halo2_verifier_code = gen_evm_verifier_sol_code::<AggregationCircuit, SHPLONK>(
841            &params,
842            pinning.pk.get_vk(),
843            pinning.metadata.num_pvs.clone(),
844        );
845
846        let wrapper_pvs = halo2_pk.wrapper.pinning.metadata.num_pvs.clone();
847        let pvs_length = match wrapper_pvs.first() {
848            // We subtract 14 to exclude the KZG accumulator and the app exe
849            // and vm commits.
850            Some(v) => v
851                .checked_sub(14)
852                .expect("Unexpected number of static verifier wrapper public values"),
853            _ => panic!("Unexpected amount of instance columns in the static verifier wrapper"),
854        };
855
856        assert!(
857            pvs_length <= 8192,
858            "OpenVM Halo2 verifier contract does not support more than 8192 public values"
859        );
860
861        // Fill out the public values length and OpenVM version in the template
862        let openvm_verifier_code = EVM_HALO2_VERIFIER_TEMPLATE
863            .replace("{PUBLIC_VALUES_LENGTH}", &pvs_length.to_string())
864            .replace("{OPENVM_VERSION}", OPENVM_VERSION);
865
866        let formatter_config = FormatterConfig {
867            line_length: 120,
868            tab_width: 4,
869            bracket_spacing: true,
870            int_types: IntTypes::Long,
871            multiline_func_header: MultilineFuncHeaderStyle::AttributesFirst,
872            quote_style: QuoteStyle::Double,
873            number_underscore: NumberUnderscore::Thousands,
874            single_line_statement_blocks: SingleLineBlockStyle::Preserve,
875            override_spacing: false,
876            wrap_comments: false,
877            ignore: vec![],
878            contract_new_lines: false,
879        };
880
881        let parsed_interface =
882            parse(EVM_HALO2_VERIFIER_INTERFACE).expect("Failed to parse interface");
883        let parsed_halo2_verifier_code =
884            parse(&halo2_verifier_code).expect("Failed to parse halo2 verifier code");
885        let parsed_openvm_verifier_code =
886            parse(&openvm_verifier_code).expect("Failed to parse openvm verifier code");
887
888        let mut formatted_interface = String::new();
889        let mut formatted_halo2_verifier_code = String::new();
890        let mut formatted_openvm_verifier_code = String::new();
891
892        format(
893            &mut formatted_interface,
894            parsed_interface,
895            formatter_config.clone(),
896        )
897        .expect("Failed to format interface");
898        format(
899            &mut formatted_halo2_verifier_code,
900            parsed_halo2_verifier_code,
901            formatter_config.clone(),
902        )
903        .expect("Failed to format halo2 verifier code");
904        format(
905            &mut formatted_openvm_verifier_code,
906            parsed_openvm_verifier_code,
907            formatter_config,
908        )
909        .expect("Failed to format openvm verifier code");
910
911        // Create temp dir
912        let temp_dir = tempdir()
913            .wrap_err("Failed to create temp dir")
914            .map_err(SdkError::Other)?;
915        let temp_path = temp_dir.path();
916        let root_path = Path::new("src").join(format!("v{}", OPENVM_VERSION));
917
918        // Make interfaces dir
919        let interfaces_path = root_path.join("interfaces");
920
921        // This will also create the dir for root_path, so no need to explicitly
922        // create it
923        create_dir_all(temp_path.join(&interfaces_path))?;
924
925        let interface_file_path = interfaces_path.join(EVM_HALO2_VERIFIER_INTERFACE_NAME);
926        let parent_file_path = root_path.join(EVM_HALO2_VERIFIER_PARENT_NAME);
927        let base_file_path = root_path.join(EVM_HALO2_VERIFIER_BASE_NAME);
928
929        // Write the files to the temp dir. This is only for compilation
930        // purposes.
931        write(temp_path.join(&interface_file_path), &formatted_interface)?;
932        write(
933            temp_path.join(&parent_file_path),
934            &formatted_halo2_verifier_code,
935        )?;
936        write(
937            temp_path.join(&base_file_path),
938            &formatted_openvm_verifier_code,
939        )?;
940
941        // Run solc from the temp dir
942        let solc_input = json!({
943            "language": "Solidity",
944            "sources": {
945                interface_file_path.to_str().unwrap(): {
946                    "content": formatted_interface
947                },
948                parent_file_path.to_str().unwrap(): {
949                    "content": formatted_halo2_verifier_code
950                },
951                base_file_path.to_str().unwrap(): {
952                    "content": formatted_openvm_verifier_code
953                }
954            },
955            "settings": {
956                "remappings": ["forge-std/=lib/forge-std/src/"],
957                "optimizer": {
958                    "enabled": true,
959                    "runs": 100000,
960                    "details": {
961                        "constantOptimizer": false,
962                        "yul": false
963                    }
964                },
965                "evmVersion": "paris",
966                "viaIR": false,
967                "outputSelection": {
968                    "*": {
969                        "*": ["metadata", "evm.bytecode.object"]
970                    }
971                }
972            }
973        });
974
975        let mut child = Command::new("solc")
976            .current_dir(temp_path)
977            .arg("--standard-json")
978            .stdin(Stdio::piped())
979            .stdout(Stdio::piped())
980            .stderr(Stdio::piped())
981            .spawn()
982            .expect("Failed to spawn solc");
983
984        child
985            .stdin
986            .as_mut()
987            .expect("Failed to open stdin")
988            .write_all(solc_input.to_string().as_bytes())
989            .expect("Failed to write to stdin");
990
991        let output = child.wait_with_output().expect("Failed to read output");
992
993        if !output.status.success() {
994            return Err(SdkError::Other(eyre::eyre!(
995                "solc exited with status {}: {}",
996                output.status,
997                String::from_utf8_lossy(&output.stderr)
998            )));
999        }
1000
1001        let parsed: Value =
1002            serde_json::from_slice(&output.stdout).map_err(|e| SdkError::Other(e.into()))?;
1003
1004        let bytecode = parsed
1005            .get("contracts")
1006            .expect("No 'contracts' field found")
1007            .get(format!("src/v{}/OpenVmHalo2Verifier.sol", OPENVM_VERSION))
1008            .unwrap_or_else(|| {
1009                panic!(
1010                    "No 'src/v{}/OpenVmHalo2Verifier.sol' field found",
1011                    OPENVM_VERSION
1012                )
1013            })
1014            .get("OpenVmHalo2Verifier")
1015            .expect("No 'OpenVmHalo2Verifier' field found")
1016            .get("evm")
1017            .expect("No 'evm' field found")
1018            .get("bytecode")
1019            .expect("No 'bytecode' field found")
1020            .get("object")
1021            .expect("No 'object' field found")
1022            .as_str()
1023            .expect("No 'object' field found");
1024
1025        let bytecode = hex::decode(bytecode).expect("Invalid hex in Binary");
1026
1027        let evm_verifier = EvmHalo2Verifier {
1028            halo2_verifier_code: formatted_halo2_verifier_code,
1029            openvm_verifier_code: formatted_openvm_verifier_code,
1030            openvm_verifier_interface: formatted_interface,
1031            artifact: EvmVerifierByteCode {
1032                sol_compiler_version: "0.8.19".to_string(),
1033                sol_compiler_options: solc_input.get("settings").unwrap().to_string(),
1034                bytecode,
1035            },
1036        };
1037        Ok(evm_verifier)
1038    }
1039
1040    #[cfg(feature = "evm-verify")]
1041    /// Uses the `verify(..)` interface of the `OpenVmHalo2Verifier` contract.
1042    pub fn verify_evm_halo2_proof(
1043        openvm_verifier: &types::EvmHalo2Verifier,
1044        evm_proof: EvmProof,
1045    ) -> Result<u64, SdkError> {
1046        let calldata = evm_proof.verifier_calldata();
1047        let deployment_code = openvm_verifier.artifact.bytecode.clone();
1048
1049        let gas_cost = snark_verifier::loader::evm::deploy_and_call(deployment_code, calldata)
1050            .map_err(|reason| {
1051                SdkError::Other(eyre::eyre!("Sdk::verify_openvm_evm_proof: {reason:?}"))
1052            })?;
1053
1054        Ok(gas_cost)
1055    }
1056}