1use 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 #[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!("{}.leaf.{}.proof", program_name, index),
97 ProofType::Internal => format!("{}.internal.{}.proof", program_name, index),
98 };
99
100 let proof_bytes = fs::read(fixtures_dir.join(proof_filename))
101 .unwrap_or_else(|_| panic!("No {:?} proof available at index {}", proof_type, index));
102 let proof: Proof<SC> = bitcode::deserialize(&proof_bytes).unwrap();
103 Ok(proof)
104}
105
106fn get_internal_verifier_proof_indices(
109 internal_verifier_index: usize,
110 leaf_proof_count: usize,
111 num_children: usize,
112) -> (ProofType, Vec<usize>) {
113 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 loop {
121 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 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 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 current_layer_proofs == 1 {
159 break;
160 }
161 }
162
163 let last_regular_internal_idx = internal_node_idx;
166 if internal_verifier_index >= last_regular_internal_idx {
167 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 } else {
175 internal_verifier_index - 1 };
177
178 return (ProofType::Internal, vec![proof_to_wrap_idx]);
179 }
180
181 panic!(
182 "Invalid internal verifier index {}",
183 internal_verifier_index
184 );
185}
186
187fn main() -> Result<()> {
188 let cli = Cli::parse();
189
190 let filter = if cli.verbose {
192 EnvFilter::from_default_env()
193 } else {
194 EnvFilter::new("info")
195 };
196 fmt::fmt().with_env_filter(filter).init();
197
198 let fixtures_dir = get_fixtures_dir();
199 let app_proof_bytes =
200 fs::read(fixtures_dir.join(format!("{}.app.proof", cli.program))).unwrap();
201 let app_proof: ContinuationVmProof<SC> = bitcode::deserialize(&app_proof_bytes).unwrap();
202
203 match cli.verifier {
204 VerifierType::Leaf => {
205 let leaf_exe_bytes =
206 fs::read(fixtures_dir.join(format!("{}.leaf.exe", cli.program))).unwrap();
207 let leaf_exe: VmExe<BabyBear> = bitcode::deserialize(&leaf_exe_bytes).unwrap();
208
209 let leaf_pk_bytes =
210 fs::read(fixtures_dir.join(format!("{}.leaf.pk", cli.program))).unwrap();
211 let leaf_pk = bitcode::deserialize(&leaf_pk_bytes).unwrap();
212
213 let leaf_inputs = LeafVmVerifierInput::chunk_continuation_vm_proof(
214 &app_proof,
215 DEFAULT_NUM_CHILDREN_LEAF,
216 );
217 let index = cli.index.unwrap_or(0);
218 let leaf_input = leaf_inputs
219 .get(index)
220 .unwrap_or_else(|| panic!("No leaf input available at index {}", index));
221
222 let agg_config = AggregationConfig::default();
223 let config = agg_config.leaf_vm_config();
224 let engine = BabyBearPoseidon2Engine::new(agg_config.leaf_fri_params);
225 let d_pk = engine.device().transport_pk_to_device(&leaf_pk);
226 let vm = VirtualMachine::new(engine, NativeCpuBuilder, config, d_pk)?;
227 let input_stream = leaf_input.write_to_stream();
228
229 execute_verifier(cli.mode, vm, &leaf_exe, input_stream)?;
230 }
231 VerifierType::Internal => {
232 let internal_exe_bytes =
233 fs::read(fixtures_dir.join(format!("{}.internal.exe", cli.program))).unwrap();
234 let internal_exe: VmExe<BabyBear> = bitcode::deserialize(&internal_exe_bytes).unwrap();
235
236 let internal_pk_bytes =
237 fs::read(fixtures_dir.join(format!("{}.internal.pk", cli.program))).unwrap();
238 let internal_pk = bitcode::deserialize(&internal_pk_bytes).unwrap();
239
240 let index = cli.index.unwrap_or(0);
241
242 let leaf_proof_count = {
244 let mut count = 0;
245 while fixtures_dir
246 .join(format!("{}.leaf.{}.proof", cli.program, count))
247 .exists()
248 {
249 count += 1;
250 }
251 count
252 };
253
254 let (proof_type, proof_indices) = get_internal_verifier_proof_indices(
256 index,
257 leaf_proof_count,
258 DEFAULT_NUM_CHILDREN_INTERNAL,
259 );
260
261 let proofs: Vec<_> = proof_indices
263 .into_iter()
264 .map(|idx| load_proof(&fixtures_dir, &cli.program, proof_type.clone(), idx))
265 .collect::<Result<Vec<_>, _>>()?;
266
267 tracing::info!(
268 "Internal verifier {} will aggregate {} {:?} proofs",
269 index,
270 proofs.len(),
271 proof_type
272 );
273
274 let agg_config = AggregationConfig::default();
275 let config = agg_config.internal_vm_config();
276 let engine = BabyBearPoseidon2Engine::new(agg_config.internal_fri_params);
277
278 let internal_committed_exe =
279 VmCommittedExe::<SC>::commit(internal_exe, engine.config().pcs());
280 let internal_inputs = InternalVmVerifierInput::chunk_leaf_or_internal_proofs(
281 internal_committed_exe.get_program_commit().into(),
282 &proofs,
283 DEFAULT_NUM_CHILDREN_INTERNAL,
284 );
285 let d_pk = engine.device().transport_pk_to_device(&internal_pk);
286 let vm = VirtualMachine::new(engine, NativeCpuBuilder, config, d_pk)?;
287 let input_stream = internal_inputs.first().unwrap().write();
288
289 execute_verifier(cli.mode, vm, &internal_committed_exe.exe, input_stream)?;
290 }
291 #[cfg(feature = "evm-prove")]
292 VerifierType::Root => {
293 let root_exe_bytes =
294 fs::read(fixtures_dir.join(format!("{}.root.exe", cli.program))).unwrap();
295 let root_exe: VmExe<BabyBear> = bitcode::deserialize(&root_exe_bytes).unwrap();
296
297 let root_pk_bytes =
298 fs::read(fixtures_dir.join(format!("{}.root.pk", cli.program))).unwrap();
299 let root_pk = bitcode::deserialize(&root_pk_bytes).unwrap();
300
301 let root_input_bytes =
303 fs::read(fixtures_dir.join(format!("{}.root.input", cli.program))).unwrap();
304 let root_input: RootVmVerifierInput<SC> =
305 bitcode::deserialize(&root_input_bytes).unwrap();
306
307 let agg_config = AggregationConfig::default();
308 let config = agg_config.root_verifier_vm_config();
309 let engine = BabyBearPoseidon2Engine::new(agg_config.root_fri_params);
310 let d_pk = engine.device().transport_pk_to_device(&root_pk);
311 let vm = VirtualMachine::new(engine, NativeCpuBuilder, config, d_pk)?;
312 let input_stream = root_input.write();
313
314 execute_verifier(cli.mode, vm, &root_exe, input_stream)?;
315 }
316 }
317
318 Ok(())
319}
320
321fn execute_verifier(
322 mode: ExecutionMode,
323 vm: VirtualMachine<BabyBearPoseidon2Engine, NativeCpuBuilder>,
324 exe: &VmExe<BabyBear>,
325 input_stream: impl Into<Streams<BabyBear>>,
326) -> Result<()> {
327 match mode {
328 ExecutionMode::Pure => {
329 tracing::info!("Running pure execute...");
330 let interpreter = vm.executor().instance(exe)?;
331 interpreter.execute(input_stream, None)?;
332 }
333 ExecutionMode::Metered => {
334 tracing::info!("Running metered execute...");
335 let ctx = vm.build_metered_ctx();
336 let interpreter = vm.metered_interpreter(exe)?;
337 interpreter.execute_metered(input_stream, ctx)?;
338 }
339 ExecutionMode::Preflight => {
340 tracing::info!("Running preflight execute...");
341 let state = vm.create_initial_state(exe, input_stream);
342 let mut interpreter = vm.preflight_interpreter(exe)?;
343 vm.execute_preflight(&mut interpreter, state, None, NATIVE_MAX_TRACE_HEIGHTS)?;
344 }
345 }
346 Ok(())
347}