1use std::{fs::read, marker::PhantomData, path::Path, sync::Arc};
2
3#[cfg(feature = "evm-verify")]
4use alloy_sol_types::sol;
5use commit::commit_app_exe;
6use config::{AggregationTreeConfig, AppConfig};
7use eyre::Result;
8use keygen::{AppProvingKey, AppVerifyingKey};
9use openvm_build::{
10 build_guest_package, find_unique_executable, get_package, GuestOptions, TargetFilter,
11};
12use openvm_circuit::{
13 arch::{
14 hasher::poseidon2::vm_poseidon2_hasher, instructions::exe::VmExe, verify_segments,
15 ContinuationVmProof, ExecutionError, VerifiedExecutionPayload, VmConfig, VmExecutor,
16 VmVerificationError,
17 },
18 system::{
19 memory::{tree::public_values::extract_public_values, CHUNK},
20 program::trace::VmCommittedExe,
21 },
22};
23use openvm_continuations::verifier::root::types::RootVmVerifierInput;
24pub use openvm_continuations::{
25 static_verifier::{DefaultStaticVerifierPvHandler, StaticVerifierPvHandler},
26 RootSC, C, F, SC,
27};
28use openvm_native_recursion::halo2::utils::Halo2ParamsReader;
29use openvm_stark_backend::proof::Proof;
30use openvm_stark_sdk::{
31 config::{baby_bear_poseidon2::BabyBearPoseidon2Engine, FriParameters},
32 engine::StarkFriEngine,
33 openvm_stark_backend::{verifier::VerificationError, Chip},
34};
35use openvm_transpiler::{
36 elf::Elf,
37 openvm_platform::memory::MEM_SIZE,
38 transpiler::{Transpiler, TranspilerError},
39 FromElf,
40};
41#[cfg(feature = "evm-verify")]
42use snark_verifier_sdk::{evm::gen_evm_verifier_sol_code, halo2::aggregation::AggregationCircuit};
43
44use crate::{
45 config::AggConfig,
46 keygen::{AggProvingKey, AggStarkProvingKey},
47 prover::{AppProver, StarkProver},
48};
49#[cfg(feature = "evm-prove")]
50use crate::{prover::EvmHalo2Prover, types::EvmProof};
51
52pub mod codec;
53pub mod commit;
54pub mod config;
55pub mod keygen;
56pub mod prover;
57
58mod stdin;
59pub use stdin::*;
60
61pub mod fs;
62pub mod types;
63
64pub type NonRootCommittedExe = VmCommittedExe<SC>;
65
66pub const EVM_HALO2_VERIFIER_INTERFACE: &str =
67 include_str!("../contracts/src/IOpenVmHalo2Verifier.sol");
68pub const EVM_HALO2_VERIFIER_TEMPLATE: &str =
69 include_str!("../contracts/template/OpenVmHalo2Verifier.sol");
70
71#[cfg(feature = "evm-verify")]
72sol! {
73 IOpenVmHalo2Verifier,
74 concat!(env!("CARGO_MANIFEST_DIR"), "/contracts/abi/IOpenVmHalo2Verifier.json"),
75}
76
77pub struct VerifiedContinuationVmPayload {
80 pub exe_commit: [F; CHUNK],
88 pub user_public_values: Vec<F>,
89}
90
91pub struct GenericSdk<E: StarkFriEngine<SC>> {
92 agg_tree_config: AggregationTreeConfig,
93 _phantom: PhantomData<E>,
94}
95
96impl<E: StarkFriEngine<SC>> Default for GenericSdk<E> {
97 fn default() -> Self {
98 Self {
99 agg_tree_config: AggregationTreeConfig::default(),
100 _phantom: PhantomData,
101 }
102 }
103}
104
105pub type Sdk = GenericSdk<BabyBearPoseidon2Engine>;
106
107impl<E: StarkFriEngine<SC>> GenericSdk<E> {
108 pub fn new() -> Self {
109 Self::default()
110 }
111
112 pub fn agg_tree_config(&self) -> &AggregationTreeConfig {
113 &self.agg_tree_config
114 }
115
116 pub fn set_agg_tree_config(&mut self, agg_tree_config: AggregationTreeConfig) {
117 self.agg_tree_config = agg_tree_config;
118 }
119
120 pub fn build<P: AsRef<Path>>(
121 &self,
122 guest_opts: GuestOptions,
123 pkg_dir: P,
124 target_filter: &Option<TargetFilter>,
125 ) -> Result<Elf> {
126 let pkg = get_package(pkg_dir.as_ref());
127 let target_dir = match build_guest_package(&pkg, &guest_opts, None, target_filter) {
128 Ok(target_dir) => target_dir,
129 Err(Some(code)) => {
130 return Err(eyre::eyre!("Failed to build guest: code = {}", code));
131 }
132 Err(None) => {
133 return Err(eyre::eyre!(
134 "Failed to build guest (OPENVM_SKIP_BUILD is set)"
135 ));
136 }
137 };
138
139 let elf_path = find_unique_executable(pkg_dir, target_dir, target_filter)?;
140 let data = read(&elf_path)?;
141 Elf::decode(&data, MEM_SIZE as u32)
142 }
143
144 pub fn transpile(
145 &self,
146 elf: Elf,
147 transpiler: Transpiler<F>,
148 ) -> Result<VmExe<F>, TranspilerError> {
149 VmExe::from_elf(elf, transpiler)
150 }
151
152 pub fn execute<VC: VmConfig<F>>(
153 &self,
154 exe: VmExe<F>,
155 vm_config: VC,
156 inputs: StdIn,
157 ) -> Result<Vec<F>, ExecutionError>
158 where
159 VC::Executor: Chip<SC>,
160 VC::Periphery: Chip<SC>,
161 {
162 let vm = VmExecutor::new(vm_config);
163 let final_memory = vm.execute(exe, inputs)?;
164 let public_values = extract_public_values(
165 &vm.config.system().memory_config.memory_dimensions(),
166 vm.config.system().num_public_values,
167 final_memory.as_ref().unwrap(),
168 );
169 Ok(public_values)
170 }
171
172 pub fn commit_app_exe(
173 &self,
174 app_fri_params: FriParameters,
175 exe: VmExe<F>,
176 ) -> Result<Arc<NonRootCommittedExe>> {
177 let committed_exe = commit_app_exe(app_fri_params, exe);
178 Ok(committed_exe)
179 }
180
181 pub fn app_keygen<VC: VmConfig<F>>(&self, config: AppConfig<VC>) -> Result<AppProvingKey<VC>>
182 where
183 VC::Executor: Chip<SC>,
184 VC::Periphery: Chip<SC>,
185 {
186 let app_pk = AppProvingKey::keygen(config);
187 Ok(app_pk)
188 }
189
190 pub fn generate_app_proof<VC: VmConfig<F>>(
191 &self,
192 app_pk: Arc<AppProvingKey<VC>>,
193 app_committed_exe: Arc<NonRootCommittedExe>,
194 inputs: StdIn,
195 ) -> Result<ContinuationVmProof<SC>>
196 where
197 VC::Executor: Chip<SC>,
198 VC::Periphery: Chip<SC>,
199 {
200 let app_prover = AppProver::<VC, E>::new(app_pk.app_vm_pk.clone(), app_committed_exe);
201 let proof = app_prover.generate_app_proof(inputs);
202 Ok(proof)
203 }
204
205 pub fn verify_app_proof(
214 &self,
215 app_vk: &AppVerifyingKey,
216 proof: &ContinuationVmProof<SC>,
217 ) -> Result<VerifiedContinuationVmPayload, VmVerificationError> {
218 let engine = E::new(app_vk.fri_params);
219 let VerifiedExecutionPayload {
220 exe_commit,
221 final_memory_root,
222 } = verify_segments(&engine, &app_vk.app_vm_vk, &proof.per_segment)?;
223
224 let hasher = vm_poseidon2_hasher();
225 proof
226 .user_public_values
227 .verify(&hasher, app_vk.memory_dimensions, final_memory_root)?;
228
229 Ok(VerifiedContinuationVmPayload {
230 exe_commit,
231 user_public_values: proof.user_public_values.public_values.clone(),
232 })
233 }
234
235 pub fn verify_app_proof_without_continuations(
236 &self,
237 app_vk: &AppVerifyingKey,
238 proof: &Proof<SC>,
239 ) -> Result<(), VerificationError> {
240 let e = E::new(app_vk.fri_params);
241 e.verify(&app_vk.app_vm_vk, proof)
242 }
243
244 pub fn agg_keygen(
245 &self,
246 config: AggConfig,
247 reader: &impl Halo2ParamsReader,
248 pv_handler: &impl StaticVerifierPvHandler,
249 ) -> Result<AggProvingKey> {
250 let agg_pk = AggProvingKey::keygen(config, reader, pv_handler);
251 Ok(agg_pk)
252 }
253
254 pub fn generate_root_verifier_input<VC: VmConfig<F>>(
255 &self,
256 app_pk: Arc<AppProvingKey<VC>>,
257 app_exe: Arc<NonRootCommittedExe>,
258 agg_stark_pk: AggStarkProvingKey,
259 inputs: StdIn,
260 ) -> Result<RootVmVerifierInput<SC>>
261 where
262 VC::Executor: Chip<SC>,
263 VC::Periphery: Chip<SC>,
264 {
265 let stark_prover =
266 StarkProver::<VC, E>::new(app_pk, app_exe, agg_stark_pk, self.agg_tree_config);
267 let proof = stark_prover.generate_root_verifier_input(inputs);
268 Ok(proof)
269 }
270
271 #[cfg(feature = "evm-prove")]
272 pub fn generate_evm_proof<VC: VmConfig<F>>(
273 &self,
274 reader: &impl Halo2ParamsReader,
275 app_pk: Arc<AppProvingKey<VC>>,
276 app_exe: Arc<NonRootCommittedExe>,
277 agg_pk: AggProvingKey,
278 inputs: StdIn,
279 ) -> Result<EvmProof>
280 where
281 VC::Executor: Chip<SC>,
282 VC::Periphery: Chip<SC>,
283 {
284 let e2e_prover =
285 EvmHalo2Prover::<VC, E>::new(reader, app_pk, app_exe, agg_pk, self.agg_tree_config);
286 let proof = e2e_prover.generate_proof_for_evm(inputs);
287 Ok(proof)
288 }
289
290 #[cfg(feature = "evm-verify")]
291 pub fn generate_halo2_verifier_solidity(
292 &self,
293 reader: &impl Halo2ParamsReader,
294 agg_pk: &AggProvingKey,
295 ) -> Result<types::EvmHalo2Verifier> {
296 use std::{
297 fs::{create_dir_all, write},
298 io::Write,
299 process::{Command, Stdio},
300 };
301
302 use eyre::Context;
303 use forge_fmt::{
304 format, parse, FormatterConfig, IntTypes, MultilineFuncHeaderStyle, NumberUnderscore,
305 QuoteStyle, SingleLineBlockStyle,
306 };
307 use openvm_native_recursion::halo2::wrapper::EvmVerifierByteCode;
308 use serde_json::{json, Value};
309 use snark_verifier::halo2_base::halo2_proofs::poly::commitment::Params;
310 use snark_verifier_sdk::SHPLONK;
311 use tempfile::tempdir;
312 use types::EvmHalo2Verifier;
313
314 use crate::fs::{
315 EVM_HALO2_VERIFIER_BASE_NAME, EVM_HALO2_VERIFIER_INTERFACE_NAME,
316 EVM_HALO2_VERIFIER_PARENT_NAME,
317 };
318
319 let params = reader.read_params(agg_pk.halo2_pk.wrapper.pinning.metadata.config_params.k);
320 let pinning = &agg_pk.halo2_pk.wrapper.pinning;
321
322 assert_eq!(
323 pinning.metadata.config_params.k as u32,
324 params.k(),
325 "Provided params don't match circuit config"
326 );
327
328 let halo2_verifier_code = gen_evm_verifier_sol_code::<AggregationCircuit, SHPLONK>(
329 ¶ms,
330 pinning.pk.get_vk(),
331 pinning.metadata.num_pvs.clone(),
332 );
333
334 let wrapper_pvs = agg_pk.halo2_pk.wrapper.pinning.metadata.num_pvs.clone();
335 let pvs_length = match wrapper_pvs.first() {
336 Some(v) => v
339 .checked_sub(14)
340 .expect("Unexpected number of static verifier wrapper public values"),
341 _ => panic!("Unexpected amount of instance columns in the static verifier wrapper"),
342 };
343
344 assert!(
345 pvs_length <= 8192,
346 "OpenVM Halo2 verifier contract does not support more than 8192 public values"
347 );
348
349 let openvm_version = env!("CARGO_PKG_VERSION");
350
351 let openvm_verifier_code = EVM_HALO2_VERIFIER_TEMPLATE
353 .replace("{PUBLIC_VALUES_LENGTH}", &pvs_length.to_string())
354 .replace("{OPENVM_VERSION}", openvm_version);
355
356 let formatter_config = FormatterConfig {
357 line_length: 120,
358 tab_width: 4,
359 bracket_spacing: true,
360 int_types: IntTypes::Long,
361 multiline_func_header: MultilineFuncHeaderStyle::AttributesFirst,
362 quote_style: QuoteStyle::Double,
363 number_underscore: NumberUnderscore::Thousands,
364 single_line_statement_blocks: SingleLineBlockStyle::Preserve,
365 override_spacing: false,
366 wrap_comments: false,
367 ignore: vec![],
368 contract_new_lines: false,
369 };
370
371 let parsed_interface =
372 parse(EVM_HALO2_VERIFIER_INTERFACE).expect("Failed to parse interface");
373 let parsed_halo2_verifier_code =
374 parse(&halo2_verifier_code).expect("Failed to parse halo2 verifier code");
375 let parsed_openvm_verifier_code =
376 parse(&openvm_verifier_code).expect("Failed to parse openvm verifier code");
377
378 let mut formatted_interface = String::new();
379 let mut formatted_halo2_verifier_code = String::new();
380 let mut formatted_openvm_verifier_code = String::new();
381
382 format(
383 &mut formatted_interface,
384 parsed_interface,
385 formatter_config.clone(),
386 )
387 .expect("Failed to format interface");
388 format(
389 &mut formatted_halo2_verifier_code,
390 parsed_halo2_verifier_code,
391 formatter_config.clone(),
392 )
393 .expect("Failed to format halo2 verifier code");
394 format(
395 &mut formatted_openvm_verifier_code,
396 parsed_openvm_verifier_code,
397 formatter_config,
398 )
399 .expect("Failed to format openvm verifier code");
400
401 let temp_dir = tempdir().wrap_err("Failed to create temp dir")?;
403 let temp_path = temp_dir.path();
404 let root_path = Path::new("src").join(format!("v{}", openvm_version));
405
406 let interfaces_path = root_path.join("interfaces");
408
409 create_dir_all(temp_path.join(&interfaces_path))?;
412
413 let interface_file_path = interfaces_path.join(EVM_HALO2_VERIFIER_INTERFACE_NAME);
414 let parent_file_path = root_path.join(EVM_HALO2_VERIFIER_PARENT_NAME);
415 let base_file_path = root_path.join(EVM_HALO2_VERIFIER_BASE_NAME);
416
417 write(temp_path.join(&interface_file_path), &formatted_interface)?;
420 write(
421 temp_path.join(&parent_file_path),
422 &formatted_halo2_verifier_code,
423 )?;
424 write(
425 temp_path.join(&base_file_path),
426 &formatted_openvm_verifier_code,
427 )?;
428
429 let solc_input = json!({
431 "language": "Solidity",
432 "sources": {
433 interface_file_path.to_str().unwrap(): {
434 "content": formatted_interface
435 },
436 parent_file_path.to_str().unwrap(): {
437 "content": formatted_halo2_verifier_code
438 },
439 base_file_path.to_str().unwrap(): {
440 "content": formatted_openvm_verifier_code
441 }
442 },
443 "settings": {
444 "remappings": ["forge-std/=lib/forge-std/src/"],
445 "optimizer": {
446 "enabled": true,
447 "runs": 100000,
448 "details": {
449 "constantOptimizer": false,
450 "yul": false
451 }
452 },
453 "evmVersion": "paris",
454 "viaIR": false,
455 "outputSelection": {
456 "*": {
457 "*": ["metadata", "evm.bytecode.object"]
458 }
459 }
460 }
461 });
462
463 let mut child = Command::new("solc")
464 .current_dir(temp_path)
465 .arg("--standard-json")
466 .stdin(Stdio::piped())
467 .stdout(Stdio::piped())
468 .stderr(Stdio::piped())
469 .spawn()
470 .expect("Failed to spawn solc");
471
472 child
473 .stdin
474 .as_mut()
475 .expect("Failed to open stdin")
476 .write_all(solc_input.to_string().as_bytes())
477 .expect("Failed to write to stdin");
478
479 let output = child.wait_with_output().expect("Failed to read output");
480
481 if !output.status.success() {
482 eyre::bail!(
483 "solc exited with status {}: {}",
484 output.status,
485 String::from_utf8_lossy(&output.stderr)
486 );
487 }
488
489 let parsed: Value = serde_json::from_slice(&output.stdout)?;
490
491 let bytecode = parsed
492 .get("contracts")
493 .expect("No 'contracts' field found")
494 .get(format!("src/v{}/OpenVmHalo2Verifier.sol", openvm_version))
495 .unwrap_or_else(|| {
496 panic!(
497 "No 'src/v{}/OpenVmHalo2Verifier.sol' field found",
498 openvm_version
499 )
500 })
501 .get("OpenVmHalo2Verifier")
502 .expect("No 'OpenVmHalo2Verifier' field found")
503 .get("evm")
504 .expect("No 'evm' field found")
505 .get("bytecode")
506 .expect("No 'bytecode' field found")
507 .get("object")
508 .expect("No 'object' field found")
509 .as_str()
510 .expect("No 'object' field found");
511
512 let bytecode = hex::decode(bytecode).expect("Invalid hex in Binary");
513
514 let evm_verifier = EvmHalo2Verifier {
515 halo2_verifier_code: formatted_halo2_verifier_code,
516 openvm_verifier_code: formatted_openvm_verifier_code,
517 openvm_verifier_interface: formatted_interface,
518 artifact: EvmVerifierByteCode {
519 sol_compiler_version: "0.8.19".to_string(),
520 sol_compiler_options: solc_input.get("settings").unwrap().to_string(),
521 bytecode,
522 },
523 };
524 Ok(evm_verifier)
525 }
526
527 #[cfg(feature = "evm-verify")]
528 pub fn verify_evm_halo2_proof(
530 &self,
531 openvm_verifier: &types::EvmHalo2Verifier,
532 evm_proof: EvmProof,
533 ) -> Result<u64> {
534 let calldata = evm_proof.verifier_calldata();
535 let deployment_code = openvm_verifier.artifact.bytecode.clone();
536
537 let gas_cost = snark_verifier::loader::evm::deploy_and_call(deployment_code, calldata)
538 .map_err(|reason| eyre::eyre!("Sdk::verify_openvm_evm_proof: {reason:?}"))?;
539
540 Ok(gas_cost)
541 }
542}