execute_verifier/
execute-verifier.rs

1//! OpenVM verifier execution
2//!
3//! First, generate the fixtures by running:
4//! ```bash
5//! cargo r -r --bin generate-fixtures --features generate-fixtures
6//! ```
7//!
8//! To profile this binary, build it with the profiling profile:
9//! ```bash
10//! cargo b --profile profiling --bin execute-verifier
11//! ```
12//!
13//! Then run it with samply for profiling:
14//! ```bash
15//! samply record --rate 10000 target/profiling/execute-verifier --mode preflight --verifier internal kitchen-sink
16//! ```
17
18use std::fs;
19
20use clap::{arg, Parser, ValueEnum};
21use eyre::Result;
22use openvm_benchmarks_utils::get_fixtures_dir;
23use openvm_circuit::arch::{
24    instructions::exe::VmExe, ContinuationVmProof, Streams, VirtualMachine,
25};
26#[cfg(feature = "evm-prove")]
27use openvm_continuations::verifier::root::types::RootVmVerifierInput;
28use openvm_continuations::{
29    verifier::{internal::types::InternalVmVerifierInput, leaf::types::LeafVmVerifierInput},
30    SC,
31};
32use openvm_native_circuit::{NativeCpuBuilder, NATIVE_MAX_TRACE_HEIGHTS};
33use openvm_native_recursion::hints::Hintable;
34use openvm_sdk::{
35    commit::VmCommittedExe,
36    config::{AggregationConfig, DEFAULT_NUM_CHILDREN_INTERNAL, DEFAULT_NUM_CHILDREN_LEAF},
37};
38use openvm_stark_sdk::{
39    config::baby_bear_poseidon2::BabyBearPoseidon2Engine,
40    engine::{StarkEngine, StarkFriEngine},
41    openvm_stark_backend::{
42        config::StarkGenericConfig, proof::Proof, prover::hal::DeviceDataTransporter,
43    },
44    p3_baby_bear::BabyBear,
45};
46use tracing_subscriber::{fmt, EnvFilter};
47
48#[derive(Clone, Debug, ValueEnum)]
49enum ExecutionMode {
50    Pure,
51    Metered,
52    Preflight,
53}
54
55#[derive(Clone, Debug, ValueEnum)]
56enum VerifierType {
57    Leaf,
58    Internal,
59    #[cfg(feature = "evm-prove")]
60    Root,
61}
62
63#[derive(Clone, Debug, ValueEnum)]
64enum ProofType {
65    Leaf,
66    Internal,
67}
68
69#[derive(Parser)]
70#[command(author, version, about = "OpenVM verifier execution")]
71struct Cli {
72    /// Program name to use for fixtures
73    #[arg(value_name = "PROGRAM", default_value = "kitchen-sink")]
74    program: String,
75
76    #[arg(short, long, value_enum, default_value = "leaf")]
77    verifier: VerifierType,
78
79    #[arg(short, long, value_enum, default_value = "preflight")]
80    mode: ExecutionMode,
81
82    #[arg(long, help = "Verifier index (for leaf and internal verifiers)")]
83    index: Option<usize>,
84
85    #[arg(short, long)]
86    verbose: bool,
87}
88
89fn load_proof(
90    fixtures_dir: &std::path::Path,
91    program_name: &str,
92    proof_type: ProofType,
93    index: usize,
94) -> Result<Proof<SC>> {
95    let proof_filename = match proof_type {
96        ProofType::Leaf => format!("{program_name}.leaf.{index}.proof"),
97        ProofType::Internal => format!("{program_name}.internal.{index}.proof"),
98    };
99
100    let proof_bytes = fs::read(fixtures_dir.join(proof_filename))
101        .unwrap_or_else(|_| panic!("No {proof_type:?} proof available at index {index}"));
102    let proof: Proof<SC> = bitcode::deserialize(&proof_bytes).unwrap();
103    Ok(proof)
104}
105
106/// Determines which proofs an internal verifier at given index should aggregate
107/// Returns (proof_type, indices) where indices are the proof indices to load
108fn get_internal_verifier_proof_indices(
109    internal_verifier_index: usize,
110    leaf_proof_count: usize,
111    num_children: usize,
112) -> (ProofType, Vec<usize>) {
113    // Calculate how internal proofs are generated in layers
114    let mut current_layer_proofs = leaf_proof_count;
115    let mut current_layer_start_idx = 0;
116    let mut is_leaf_layer = true;
117    let mut internal_node_idx = 0;
118
119    // First, traverse through regular aggregation layers
120    loop {
121        // Calculate number of internal proofs in current layer
122        let num_internal_proofs_in_layer = current_layer_proofs.div_ceil(num_children);
123
124        if internal_node_idx + num_internal_proofs_in_layer > internal_verifier_index {
125            // Found the layer containing our internal verifier
126            let layer_local_idx = internal_verifier_index - internal_node_idx;
127            let start_proof_idx = layer_local_idx * num_children;
128            let end_proof_idx = ((layer_local_idx + 1) * num_children).min(current_layer_proofs);
129
130            let proof_indices: Vec<usize> = (start_proof_idx..end_proof_idx)
131                .map(|i| {
132                    if is_leaf_layer {
133                        i
134                    } else {
135                        current_layer_start_idx + i
136                    }
137                })
138                .collect();
139
140            let proof_type = if is_leaf_layer {
141                ProofType::Leaf
142            } else {
143                ProofType::Internal
144            };
145
146            return (proof_type, proof_indices);
147        }
148
149        // Move to next layer
150        internal_node_idx += num_internal_proofs_in_layer;
151        if !is_leaf_layer {
152            current_layer_start_idx += current_layer_proofs;
153        }
154        current_layer_proofs = num_internal_proofs_in_layer;
155        is_leaf_layer = false;
156
157        // If we're down to 1 proof, we've reached the final internal proof
158        if current_layer_proofs == 1 {
159            break;
160        }
161    }
162
163    // If we get here, the index might be for a wrapper internal proof
164    // Wrapper proofs aggregate exactly one proof (the previous internal proof)
165    let last_regular_internal_idx = internal_node_idx;
166    if internal_verifier_index >= last_regular_internal_idx {
167        // This is a wrapper proof - it aggregates exactly one internal proof
168        // The wrapper at index `last_regular_internal_idx` wraps the final internal proof (at index
169        // last_regular_internal_idx - 1) The wrapper at index `last_regular_internal_idx +
170        // 1` wraps the wrapper at index last_regular_internal_idx
171        let wrapper_layer = internal_verifier_index - last_regular_internal_idx;
172        let proof_to_wrap_idx = if wrapper_layer == 0 {
173            last_regular_internal_idx - 1 // Wrap the final internal proof
174        } else {
175            internal_verifier_index - 1 // Wrap the previous wrapper
176        };
177
178        return (ProofType::Internal, vec![proof_to_wrap_idx]);
179    }
180
181    panic!("Invalid internal verifier index {internal_verifier_index}");
182}
183
184fn main() -> Result<()> {
185    let cli = Cli::parse();
186
187    // Set up logging
188    let filter = if cli.verbose {
189        EnvFilter::from_default_env()
190    } else {
191        EnvFilter::new("info")
192    };
193    fmt::fmt().with_env_filter(filter).init();
194
195    let fixtures_dir = get_fixtures_dir();
196    let app_proof_bytes =
197        fs::read(fixtures_dir.join(format!("{}.app.proof", cli.program))).unwrap();
198    let app_proof: ContinuationVmProof<SC> = bitcode::deserialize(&app_proof_bytes).unwrap();
199
200    match cli.verifier {
201        VerifierType::Leaf => {
202            let leaf_exe_bytes =
203                fs::read(fixtures_dir.join(format!("{}.leaf.exe", cli.program))).unwrap();
204            let leaf_exe: VmExe<BabyBear> = bitcode::deserialize(&leaf_exe_bytes).unwrap();
205
206            let leaf_pk_bytes =
207                fs::read(fixtures_dir.join(format!("{}.leaf.pk", cli.program))).unwrap();
208            let leaf_pk = bitcode::deserialize(&leaf_pk_bytes).unwrap();
209
210            let leaf_inputs = LeafVmVerifierInput::chunk_continuation_vm_proof(
211                &app_proof,
212                DEFAULT_NUM_CHILDREN_LEAF,
213            );
214            let index = cli.index.unwrap_or(0);
215            let leaf_input = leaf_inputs
216                .get(index)
217                .unwrap_or_else(|| panic!("No leaf input available at index {index}"));
218
219            let agg_config = AggregationConfig::default();
220            let config = agg_config.leaf_vm_config();
221            let engine = BabyBearPoseidon2Engine::new(agg_config.leaf_fri_params);
222            let d_pk = engine.device().transport_pk_to_device(&leaf_pk);
223            let vm = VirtualMachine::new(engine, NativeCpuBuilder, config, d_pk)?;
224            let input_stream = leaf_input.write_to_stream();
225
226            execute_verifier(cli.mode, vm, &leaf_exe, input_stream)?;
227        }
228        VerifierType::Internal => {
229            let internal_exe_bytes =
230                fs::read(fixtures_dir.join(format!("{}.internal.exe", cli.program))).unwrap();
231            let internal_exe: VmExe<BabyBear> = bitcode::deserialize(&internal_exe_bytes).unwrap();
232
233            let internal_pk_bytes =
234                fs::read(fixtures_dir.join(format!("{}.internal.pk", cli.program))).unwrap();
235            let internal_pk = bitcode::deserialize(&internal_pk_bytes).unwrap();
236
237            let index = cli.index.unwrap_or(0);
238
239            // Count available leaf proofs
240            let leaf_proof_count = {
241                let mut count = 0;
242                while fixtures_dir
243                    .join(format!("{}.leaf.{}.proof", cli.program, count))
244                    .exists()
245                {
246                    count += 1;
247                }
248                count
249            };
250
251            // Determine which proofs this internal verifier should aggregate
252            let (proof_type, proof_indices) = get_internal_verifier_proof_indices(
253                index,
254                leaf_proof_count,
255                DEFAULT_NUM_CHILDREN_INTERNAL,
256            );
257
258            // Load the determined proofs
259            let proofs: Vec<_> = proof_indices
260                .into_iter()
261                .map(|idx| load_proof(&fixtures_dir, &cli.program, proof_type.clone(), idx))
262                .collect::<Result<Vec<_>, _>>()?;
263
264            tracing::info!(
265                "Internal verifier {} will aggregate {} {:?} proofs",
266                index,
267                proofs.len(),
268                proof_type
269            );
270
271            let agg_config = AggregationConfig::default();
272            let config = agg_config.internal_vm_config();
273            let engine = BabyBearPoseidon2Engine::new(agg_config.internal_fri_params);
274
275            let internal_committed_exe =
276                VmCommittedExe::<SC>::commit(internal_exe, engine.config().pcs());
277            let internal_inputs = InternalVmVerifierInput::chunk_leaf_or_internal_proofs(
278                internal_committed_exe.get_program_commit().into(),
279                &proofs,
280                DEFAULT_NUM_CHILDREN_INTERNAL,
281            );
282            let d_pk = engine.device().transport_pk_to_device(&internal_pk);
283            let vm = VirtualMachine::new(engine, NativeCpuBuilder, config, d_pk)?;
284            let input_stream = internal_inputs.first().unwrap().write();
285
286            execute_verifier(cli.mode, vm, &internal_committed_exe.exe, input_stream)?;
287        }
288        #[cfg(feature = "evm-prove")]
289        VerifierType::Root => {
290            let root_exe_bytes =
291                fs::read(fixtures_dir.join(format!("{}.root.exe", cli.program))).unwrap();
292            let root_exe: VmExe<BabyBear> = bitcode::deserialize(&root_exe_bytes).unwrap();
293
294            let root_pk_bytes =
295                fs::read(fixtures_dir.join(format!("{}.root.pk", cli.program))).unwrap();
296            let root_pk = bitcode::deserialize(&root_pk_bytes).unwrap();
297
298            // Load root verifier input
299            let root_input_bytes =
300                fs::read(fixtures_dir.join(format!("{}.root.input", cli.program))).unwrap();
301            let root_input: RootVmVerifierInput<SC> =
302                bitcode::deserialize(&root_input_bytes).unwrap();
303
304            let agg_config = AggregationConfig::default();
305            let config = agg_config.root_verifier_vm_config();
306            let engine = BabyBearPoseidon2Engine::new(agg_config.root_fri_params);
307            let d_pk = engine.device().transport_pk_to_device(&root_pk);
308            let vm = VirtualMachine::new(engine, NativeCpuBuilder, config, d_pk)?;
309            let input_stream = root_input.write();
310
311            execute_verifier(cli.mode, vm, &root_exe, input_stream)?;
312        }
313    }
314
315    Ok(())
316}
317
318fn execute_verifier(
319    mode: ExecutionMode,
320    vm: VirtualMachine<BabyBearPoseidon2Engine, NativeCpuBuilder>,
321    exe: &VmExe<BabyBear>,
322    input_stream: impl Into<Streams<BabyBear>>,
323) -> Result<()> {
324    match mode {
325        ExecutionMode::Pure => {
326            tracing::info!("Running pure execute...");
327            let interpreter = vm.executor().instance(exe)?;
328            interpreter.execute(input_stream, None)?;
329        }
330        ExecutionMode::Metered => {
331            tracing::info!("Running metered execute...");
332            let ctx = vm.build_metered_ctx(exe);
333            let interpreter = vm.metered_interpreter(exe)?;
334            interpreter.execute_metered(input_stream, ctx)?;
335        }
336        ExecutionMode::Preflight => {
337            tracing::info!("Running preflight execute...");
338            let state = vm.create_initial_state(exe, input_stream);
339            let mut interpreter = vm.preflight_interpreter(exe)?;
340            vm.execute_preflight(&mut interpreter, state, None, NATIVE_MAX_TRACE_HEIGHTS)?;
341        }
342    }
343    Ok(())
344}