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!("{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
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!("Invalid internal verifier index {internal_verifier_index}");
182}
183
184fn main() -> Result<()> {
185 let cli = Cli::parse();
186
187 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 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 let (proof_type, proof_indices) = get_internal_verifier_proof_indices(
253 index,
254 leaf_proof_count,
255 DEFAULT_NUM_CHILDREN_INTERNAL,
256 );
257
258 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 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}