From bfe59d4810ebdcb224bb0c43740f910169995af9 Mon Sep 17 00:00:00 2001 From: Ray Gao Date: Wed, 10 Dec 2025 20:20:44 -0500 Subject: [PATCH 01/14] add root prover [skip ci] --- ceno_cli/src/sdk.rs | 21 +--- ceno_recursion/src/aggregation/internal.rs | 4 - ceno_recursion/src/aggregation/mod.rs | 118 +++++++++++++++++---- ceno_recursion/src/aggregation/root.rs | 110 +++++++++++++++++++ 4 files changed, 210 insertions(+), 43 deletions(-) diff --git a/ceno_cli/src/sdk.rs b/ceno_cli/src/sdk.rs index ee5d1d66d..bf9d0307c 100644 --- a/ceno_cli/src/sdk.rs +++ b/ceno_cli/src/sdk.rs @@ -23,8 +23,8 @@ use openvm_continuations::verifier::internal::types::VmStarkProof; #[cfg(feature = "gpu")] use openvm_cuda_backend::engine::GpuBabyBearPoseidon2Engine as BabyBearPoseidon2Engine; use openvm_native_circuit::{NativeBuilder, NativeConfig}; -use openvm_sdk::prover::vm::new_local_prover; -use openvm_stark_backend::config::StarkGenericConfig; +use openvm_sdk::{RootSC, prover::vm::new_local_prover}; +use openvm_stark_backend::{config::StarkGenericConfig, proof::Proof}; use openvm_stark_sdk::config::baby_bear_poseidon2::BabyBearPoseidon2Config; #[cfg(not(feature = "gpu"))] use openvm_stark_sdk::config::baby_bear_poseidon2::BabyBearPoseidon2Engine; @@ -194,25 +194,14 @@ where pub fn compress_to_root_proof( &mut self, base_proofs: Vec>>, - ) -> VmStarkProof { + ) -> Proof { let vb = NativeBuilder::default(); // TODO: cache agg_prover let mut agg_prover = if let Some(agg_pk) = self.agg_pk.as_ref() { - let leaf_prover = new_local_prover::( - vb.clone(), - &agg_pk.leaf_vm_pk, - agg_pk.leaf_committed_exe.exe.clone(), + CenoAggregationProver::from_base_vk( + self.zkvm_vk.clone().expect("zkvm_vk need to be set"), ) - .expect("leaf prover"); - let internal_prover = new_local_prover::( - vb.clone(), - &agg_pk.internal_vm_pk, - agg_pk.internal_committed_exe.exe.clone(), - ) - .expect("internal prover"); - - CenoAggregationProver::new(leaf_prover, internal_prover, agg_pk.clone()) } else { let agg_prover = CenoAggregationProver::from_base_vk(self.zkvm_vk.clone().unwrap()); self.agg_pk = Some(agg_prover.pk.clone()); diff --git a/ceno_recursion/src/aggregation/internal.rs b/ceno_recursion/src/aggregation/internal.rs index 825668179..bd494a0e9 100644 --- a/ceno_recursion/src/aggregation/internal.rs +++ b/ceno_recursion/src/aggregation/internal.rs @@ -108,10 +108,6 @@ impl NonLeafVerifierVariables { ); }, |builder| { - for i in 0..DIGEST_SIZE { - builder.print_f(pvs.app_commit[i]); - builder.print_f(proof_vm_pvs.vm_verifier_pvs.app_commit[i]); - } builder.assert_eq::<[_; DIGEST_SIZE]>( pvs.app_commit, proof_vm_pvs.vm_verifier_pvs.app_commit, diff --git a/ceno_recursion/src/aggregation/mod.rs b/ceno_recursion/src/aggregation/mod.rs index 27d6288c0..9bfcc7675 100644 --- a/ceno_recursion/src/aggregation/mod.rs +++ b/ceno_recursion/src/aggregation/mod.rs @@ -1,6 +1,9 @@ -use crate::zkvm_verifier::{ - binding::{E, F, ZKVMProofInput, ZKVMProofInputVariable}, - verifier::verify_zkvm_proof, +use crate::{ + aggregation::root::CenoRootVmVerifierConfig, + zkvm_verifier::{ + binding::{E, F, ZKVMProofInput, ZKVMProofInputVariable}, + verifier::verify_zkvm_proof, + }, }; use ceno_zkvm::{ instructions::riscv::constants::{END_PC_IDX, EXIT_CODE_IDX, INIT_PC_IDX}, @@ -24,6 +27,7 @@ use openvm_continuations::{ verifier::{ common::types::VmVerifierPvs, internal::types::{InternalVmVerifierInput, InternalVmVerifierPvs, VmStarkProof}, + root::types::RootVmVerifierInput, }, }; #[cfg(feature = "gpu")] @@ -69,12 +73,14 @@ use openvm_circuit::{ }, system::{memory::CHUNK, program::trace::compute_exe_commit}, }; +use openvm_continuations::RootSC; use openvm_native_compiler::{ asm::AsmConfig, ir::{Builder, Config, Felt}, }; use openvm_sdk::util::check_max_constraint_degrees; use openvm_stark_backend::proof::Proof; +use openvm_stark_sdk::config::baby_bear_poseidon2_root::BabyBearPoseidon2RootEngine; mod internal; mod root; @@ -84,6 +90,8 @@ pub type InnerConfig = AsmConfig; pub const LEAF_LOG_BLOWUP: usize = 1; pub const INTERNAL_LOG_BLOWUP: usize = 2; pub const ROOT_LOG_BLOWUP: usize = 3; +pub const ROOT_MAX_CONSTRAINT_DEG: usize = (1 << ROOT_LOG_BLOWUP) + 1; +pub const ROOT_NUM_PUBLIC_VALUES: usize = 14; pub const SBOX_SIZE: usize = 7; const VM_MAX_TRACE_HEIGHTS: &[u32] = &[ 4194304, 4, 128, 2097152, 8388608, 4194304, 262144, 8388608, 16777216, 2097152, 16777216, @@ -92,6 +100,7 @@ const VM_MAX_TRACE_HEIGHTS: &[u32] = &[ pub struct CenoAggregationProver { pub leaf_prover: VmInstance, pub internal_prover: VmInstance, + pub root_prover: VmInstance, pub vk: CenoRecursionVerifierKeys, pub pk: CenoRecursionProvingKeys, } @@ -100,11 +109,13 @@ impl CenoAggregationProver { pub fn new( leaf_prover: VmInstance, internal_prover: VmInstance, + root_prover: VmInstance, pk: CenoRecursionProvingKeys, ) -> Self { Self { leaf_prover, internal_prover, + root_prover, vk: pk.get_vk(), pk, } @@ -112,7 +123,7 @@ impl CenoAggregationProver { pub fn from_base_vk(vk: ZKVMVerifyingKey>) -> Self { let vb = NativeBuilder::default(); - let [leaf_fri_params, internal_fri_params, _root_fri_params] = + let [leaf_fri_params, internal_fri_params, root_fri_params] = [LEAF_LOG_BLOWUP, INTERNAL_LOG_BLOWUP, ROOT_LOG_BLOWUP] .map(FriParameters::standard_with_100_bits_conjectured_security); @@ -213,6 +224,8 @@ impl CenoAggregationProver { internal_program.into(), internal_vm.engine.config().pcs(), )); + let internal_vm_verifier_commit: [F; DIGEST_SIZE] = + internal_committed_exe.get_program_commit().into(); let internal_prover = new_local_prover::( vb.clone(), &internal_vm_pk, @@ -220,9 +233,59 @@ impl CenoAggregationProver { ) .expect("internal prover"); - // TODO: build root program (requires shard ram ec point is zero) - // TODO: add root prover + // Root prover + let root_vm_config = NativeConfig { + system: SystemConfig::new( + SBOX_SIZE.min(ROOT_MAX_CONSTRAINT_DEG), + MemoryConfig { + max_access_adapter_n: 16, + ..Default::default() + }, + ROOT_NUM_PUBLIC_VALUES, + ) + .without_continuations() + .with_max_segment_len((1 << 24) - 100) + .with_profiling(), + native: Default::default(), + }; + + let mut root_engine = BabyBearPoseidon2RootEngine::new(root_fri_params); + root_engine.max_constraint_degree = ROOT_MAX_CONSTRAINT_DEG; + let (mut root_vm, mut root_vm_pk) = + VirtualMachine::new_with_keygen(root_engine, vb.clone(), root_vm_config.clone()) + .expect("root keygen"); + let root_program = CenoRootVmVerifierConfig { + leaf_fri_params, + internal_fri_params, + num_user_public_values: ROOT_NUM_PUBLIC_VALUES, + internal_vm_verifier_commit, + compiler_options: CompilerOptions::default().with_cycle_tracker(), + } + .build_program(&leaf_vm_vk, &internal_vm_vk); + let root_committed_exe = Arc::new(VmCommittedExe::::commit( + root_program.into(), + root_vm.engine.config().pcs(), + )); + + // AIR Permutation? + // let air_heights = + // compute_root_proof_heights(&mut vm, &root_committed_exe, &internal_proof)?; + // let root_air_perm = AirIdPermutation::compute(&air_heights); + // root_air_perm.permute(&mut vm_pk.per_air); + + let root_vm_pk = Arc::new(VmProvingKey { + fri_params: root_fri_params, + vm_config: root_vm_config, + vm_pk: root_vm_pk, + }); + let root_prover = new_local_prover::( + vb.clone(), + &root_vm_pk, + root_committed_exe.exe.clone(), + ) + .expect("root prover"); + // Recursion keys let vk = CenoRecursionVerifierKeys { leaf_vm_vk, leaf_fri_params: leaf_vm_pk.fri_params, @@ -230,7 +293,6 @@ impl CenoAggregationProver { internal_fri_params: internal_vm_pk.fri_params, internal_commit: internal_committed_exe.get_program_commit(), }; - let pk = CenoRecursionProvingKeys { leaf_vm_pk, leaf_committed_exe, @@ -241,6 +303,7 @@ impl CenoAggregationProver { Self { leaf_prover, internal_prover, + root_prover, vk, pk, } @@ -249,7 +312,7 @@ impl CenoAggregationProver { pub fn generate_root_proof( &mut self, base_proofs: Vec>>, - ) -> VmStarkProof { + ) -> Proof { let aggregation_start_timestamp = Instant::now(); // Construct zkvm proof input @@ -356,13 +419,22 @@ impl CenoAggregationProver { ); println!("Aggregation - Final height: {:?}", internal_node_height); - // TODO: generate root proof from last internal proof + let last_internal = proofs.pop().unwrap(); - // Export e2e stark proof (used in verify_e2e_stark_proof) - VmStarkProof { - inner: proofs.pop().unwrap(), - user_public_values, - } + // TODO: wrapping for reducing AIR heights + + let root_input = RootVmVerifierInput { + proofs: vec![last_internal], + public_values: user_public_values, + }; + let root_proof = SingleSegmentVmProver::prove( + &mut self.root_prover, + root_input.write(), + VM_MAX_TRACE_HEIGHTS, + ) + .expect("root proof generation should pass"); + + root_proof } } @@ -732,16 +804,16 @@ mod tests { .expect("Failed to deserialize vk file"); let mut agg_prover = CenoAggregationProver::from_base_vk(vk); - let root_stark_proof = agg_prover.generate_root_proof(zkvm_proofs); + let root_proof = agg_prover.generate_root_proof(zkvm_proofs); // _debug - verify_e2e_stark_proof( - &agg_prover.vk, - &root_stark_proof, - &Bn254Fr::ZERO, - &Bn254Fr::ZERO, - ) - .expect("Verify e2e stark proof should pass"); + // verify_e2e_stark_proof( + // &agg_prover.vk, + // &root_stark_proof, + // &Bn254Fr::ZERO, + // &Bn254Fr::ZERO, + // ) + // .expect("Verify e2e stark proof should pass"); } pub fn verify_single_inner_thread() { @@ -780,7 +852,7 @@ mod tests { } #[test] - #[ignore = "need to generate proof first"] + // #[ignore = "need to generate proof first"] pub fn test_aggregation() { let stack_size = 256 * 1024 * 1024; // 64 MB diff --git a/ceno_recursion/src/aggregation/root.rs b/ceno_recursion/src/aggregation/root.rs index 746672af6..ca0bef790 100644 --- a/ceno_recursion/src/aggregation/root.rs +++ b/ceno_recursion/src/aggregation/root.rs @@ -4,3 +4,113 @@ // let is_sum_y_zero = ec_sum.y.is_zero(builder); // builder.assert_usize_eq(is_sum_x_zero, Usize::from(1)); // builder.assert_usize_eq(is_sum_y_zero, Usize::from(1)); + +use openvm_continuations::{ + C, F, SC, + verifier::{ + common::non_leaf::NonLeafVerifierVariables, + root::{types::RootVmVerifierInput, vars::RootVmVerifierInputVariable}, + }, +}; +use openvm_instructions::program::Program; +use openvm_native_compiler::{ + conversion::CompilerOptions, + ir::{Array, Builder, Config, DIGEST_SIZE, Felt, RVar, Variable}, +}; +use openvm_native_recursion::{ + fri::TwoAdicFriPcsVariable, hints::Hintable, types::new_from_inner_multi_vk, + utils::const_fri_config, +}; +use openvm_stark_backend::keygen::types::MultiStarkVerifyingKey; +use openvm_stark_sdk::config::FriParameters; +use std::{array, borrow::Borrow}; + +pub struct CenoRootVmVerifierConfig { + pub leaf_fri_params: FriParameters, + pub internal_fri_params: FriParameters, + pub num_user_public_values: usize, + pub internal_vm_verifier_commit: [F; DIGEST_SIZE], + pub compiler_options: CompilerOptions, +} + +#[derive(Debug)] +pub struct CenoRootVmVerifierPvs { + pub public_values: Vec, +} +impl CenoRootVmVerifierPvs { + pub fn flatten(self) -> Vec { + let mut ret = vec![]; + ret.extend(self.public_values); + ret + } +} + +impl CenoRootVmVerifierConfig { + pub fn build_program( + &self, + leaf_vm_vk: &MultiStarkVerifyingKey, + internal_vm_vk: &MultiStarkVerifyingKey, + ) -> Program { + let mut builder = Builder::::default(); + + builder.cycle_tracker_start("ReadProofsFromInput"); + let root_verifier_input = RootVmVerifierInput::::read(&mut builder); + builder.cycle_tracker_end("ReadProofsFromInput"); + + let pvs = { + let leaf_advice = new_from_inner_multi_vk(leaf_vm_vk); + let internal_advice = new_from_inner_multi_vk(internal_vm_vk); + let RootVmVerifierInputVariable { + proofs, + public_values, + } = root_verifier_input; + + builder.cycle_tracker_start("InitializePcsConst"); + let leaf_pcs = TwoAdicFriPcsVariable { + config: const_fri_config(&mut builder, &self.leaf_fri_params), + }; + let internal_pcs = TwoAdicFriPcsVariable { + config: const_fri_config(&mut builder, &self.internal_fri_params), + }; + builder.cycle_tracker_end("InitializePcsConst"); + let internal_program_commit = + array::from_fn(|i| builder.eval(self.internal_vm_verifier_commit[i])); + builder.cycle_tracker_start("VerifyProofs"); + let non_leaf_verifier = NonLeafVerifierVariables { + internal_program_commit, + leaf_pcs, + leaf_advice, + internal_pcs, + internal_advice, + }; + let (merged_pvs, expected_leaf_commit) = + non_leaf_verifier.verify_internal_or_leaf_verifier_proofs(&mut builder, &proofs); + builder.cycle_tracker_end("VerifyProofs"); + + /* _todo: Change merged pv operations, including checking final ec sum is infinity + // App Program should terminate + builder.assert_felt_eq(merged_pvs.connector.is_terminate, F::ONE); + // App Program should exit successfully + builder.assert_felt_eq(merged_pvs.connector.exit_code, F::ZERO); + */ + + builder.cycle_tracker_start("ExtractPublicValues"); + // builder.assert_usize_eq(public_values.len(), RVar::from(self.num_user_public_values)); + let public_values_vec: Vec> = (0..self.num_user_public_values) + .map(|i| builder.get(&public_values, i)) + .collect(); + builder.cycle_tracker_end("ExtractPublicValues"); + + CenoRootVmVerifierPvs { + public_values: public_values_vec, + } + }; + + pvs.flatten() + .into_iter() + .for_each(|v| builder.commit_public_value(v)); + + builder.halt(); + builder.compile_isa_with_options(self.compiler_options) + } +} From ca9153e29a0a41c6648e75c38533faced60f990b Mon Sep 17 00:00:00 2001 From: Ray Gao Date: Wed, 10 Dec 2025 20:33:57 -0500 Subject: [PATCH 02/14] use self-defined root verifier input [skip ci] --- ceno_recursion/src/aggregation/root.rs | 60 ++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/ceno_recursion/src/aggregation/root.rs b/ceno_recursion/src/aggregation/root.rs index ca0bef790..52853cbe5 100644 --- a/ceno_recursion/src/aggregation/root.rs +++ b/ceno_recursion/src/aggregation/root.rs @@ -9,7 +9,7 @@ use openvm_continuations::{ C, F, SC, verifier::{ common::non_leaf::NonLeafVerifierVariables, - root::{types::RootVmVerifierInput, vars::RootVmVerifierInputVariable}, + // root::{types::RootVmVerifierInput, vars::RootVmVerifierInputVariable}, }, }; use openvm_instructions::program::Program; @@ -25,6 +25,60 @@ use openvm_stark_backend::keygen::types::MultiStarkVerifyingKey; use openvm_stark_sdk::config::FriParameters; use std::{array, borrow::Borrow}; +use openvm_stark_sdk::{ + config::baby_bear_poseidon2::BabyBearPoseidon2Config, + openvm_stark_backend::{ + config::{Com, StarkGenericConfig, Val}, + p3_field::PrimeField32, + proof::Proof, + }, +}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use openvm_native_compiler::{ + asm::AsmConfig, + prelude::*, +}; +use openvm_native_recursion::vars::StarkProofVariable; + +#[derive(Serialize, Deserialize)] +pub struct CenoRootVmVerifierInput { + /// The proofs of leaf verifier or internal verifier in the execution order. + pub proofs: Vec>, + /// Public values to expose directly + pub public_values: Vec>, +} + +#[derive(DslVariable, Clone)] +pub struct CenoRootVmVerifierInputVariable { + /// The proofs of leaf verifier or internal verifier in the execution order. + pub proofs: Array>, + /// Public values to expose + pub public_values: Array>, +} + +impl Hintable for CenoRootVmVerifierInput { + type HintVariable = CenoRootVmVerifierInputVariable; + + fn read(builder: &mut Builder) -> Self::HintVariable { + let proofs = Vec::>::read(builder); + let public_values = Vec::>::read(builder); + Self::HintVariable { + proofs, + public_values, + } + } + + fn write(&self) -> Vec::N>> { + let mut stream = self.proofs.write(); + stream.extend(self.public_values.write()); + stream + } +} + + + + + pub struct CenoRootVmVerifierConfig { pub leaf_fri_params: FriParameters, pub internal_fri_params: FriParameters, @@ -54,13 +108,13 @@ impl CenoRootVmVerifierConfig { let mut builder = Builder::::default(); builder.cycle_tracker_start("ReadProofsFromInput"); - let root_verifier_input = RootVmVerifierInput::::read(&mut builder); + let root_verifier_input = CenoRootVmVerifierInput::::read(&mut builder); builder.cycle_tracker_end("ReadProofsFromInput"); let pvs = { let leaf_advice = new_from_inner_multi_vk(leaf_vm_vk); let internal_advice = new_from_inner_multi_vk(internal_vm_vk); - let RootVmVerifierInputVariable { + let CenoRootVmVerifierInputVariable { proofs, public_values, } = root_verifier_input; From de74651e39f5ca9b38cc0876c1e94a436ad3a187 Mon Sep 17 00:00:00 2001 From: Ray Gao Date: Wed, 10 Dec 2025 20:54:55 -0500 Subject: [PATCH 03/14] use custom internal [skip ci] --- ceno_recursion/src/aggregation/root.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/ceno_recursion/src/aggregation/root.rs b/ceno_recursion/src/aggregation/root.rs index 52853cbe5..c2bbb9fc0 100644 --- a/ceno_recursion/src/aggregation/root.rs +++ b/ceno_recursion/src/aggregation/root.rs @@ -7,10 +7,6 @@ use openvm_continuations::{ C, F, SC, - verifier::{ - common::non_leaf::NonLeafVerifierVariables, - // root::{types::RootVmVerifierInput, vars::RootVmVerifierInputVariable}, - }, }; use openvm_instructions::program::Program; use openvm_native_compiler::{ @@ -39,6 +35,7 @@ use openvm_native_compiler::{ prelude::*, }; use openvm_native_recursion::vars::StarkProofVariable; +use crate::aggregation::internal::NonLeafVerifierVariables; #[derive(Serialize, Deserialize)] pub struct CenoRootVmVerifierInput { @@ -75,10 +72,6 @@ impl Hintable for CenoRootVmVerifierInput { } } - - - - pub struct CenoRootVmVerifierConfig { pub leaf_fri_params: FriParameters, pub internal_fri_params: FriParameters, From b91155b8bab87a163557a3045ecb647487169745 Mon Sep 17 00:00:00 2001 From: Ray Gao Date: Thu, 11 Dec 2025 14:05:40 -0500 Subject: [PATCH 04/14] ignore test --- ceno_cli/src/sdk.rs | 11 +++------ ceno_recursion/src/aggregation/mod.rs | 19 +++++---------- ceno_recursion/src/aggregation/root.rs | 33 ++++++++++---------------- 3 files changed, 21 insertions(+), 42 deletions(-) diff --git a/ceno_cli/src/sdk.rs b/ceno_cli/src/sdk.rs index bf9d0307c..39d9d7a39 100644 --- a/ceno_cli/src/sdk.rs +++ b/ceno_cli/src/sdk.rs @@ -19,15 +19,12 @@ use gkr_iop::cpu::{CpuBackend, CpuProver}; use gkr_iop::gpu::{GpuBackend, GpuProver}; use gkr_iop::hal::ProverBackend; use mpcs::{Basefold, BasefoldRSParams, PolynomialCommitmentScheme, SecurityLevel}; -use openvm_continuations::verifier::internal::types::VmStarkProof; #[cfg(feature = "gpu")] use openvm_cuda_backend::engine::GpuBabyBearPoseidon2Engine as BabyBearPoseidon2Engine; -use openvm_native_circuit::{NativeBuilder, NativeConfig}; -use openvm_sdk::{RootSC, prover::vm::new_local_prover}; +use openvm_native_circuit::NativeConfig; +use openvm_sdk::RootSC; use openvm_stark_backend::{config::StarkGenericConfig, proof::Proof}; use openvm_stark_sdk::config::baby_bear_poseidon2::BabyBearPoseidon2Config; -#[cfg(not(feature = "gpu"))] -use openvm_stark_sdk::config::baby_bear_poseidon2::BabyBearPoseidon2Engine; use serde::Serialize; use std::sync::Arc; @@ -195,10 +192,8 @@ where &mut self, base_proofs: Vec>>, ) -> Proof { - let vb = NativeBuilder::default(); - // TODO: cache agg_prover - let mut agg_prover = if let Some(agg_pk) = self.agg_pk.as_ref() { + let mut agg_prover = if let Some(_agg_pk) = self.agg_pk.as_ref() { CenoAggregationProver::from_base_vk( self.zkvm_vk.clone().expect("zkvm_vk need to be set"), ) diff --git a/ceno_recursion/src/aggregation/mod.rs b/ceno_recursion/src/aggregation/mod.rs index 9bfcc7675..76ac07200 100644 --- a/ceno_recursion/src/aggregation/mod.rs +++ b/ceno_recursion/src/aggregation/mod.rs @@ -251,7 +251,7 @@ impl CenoAggregationProver { let mut root_engine = BabyBearPoseidon2RootEngine::new(root_fri_params); root_engine.max_constraint_degree = ROOT_MAX_CONSTRAINT_DEG; - let (mut root_vm, mut root_vm_pk) = + let (root_vm, root_vm_pk) = VirtualMachine::new_with_keygen(root_engine, vb.clone(), root_vm_config.clone()) .expect("root keygen"); let root_program = CenoRootVmVerifierConfig { @@ -267,12 +267,6 @@ impl CenoAggregationProver { root_vm.engine.config().pcs(), )); - // AIR Permutation? - // let air_heights = - // compute_root_proof_heights(&mut vm, &root_committed_exe, &internal_proof)?; - // let root_air_perm = AirIdPermutation::compute(&air_heights); - // root_air_perm.permute(&mut vm_pk.per_air); - let root_vm_pk = Arc::new(VmProvingKey { fri_params: root_fri_params, vm_config: root_vm_config, @@ -421,20 +415,19 @@ impl CenoAggregationProver { let last_internal = proofs.pop().unwrap(); - // TODO: wrapping for reducing AIR heights + // _todo: possible multi-layer wrapping for reducing AIR heights let root_input = RootVmVerifierInput { proofs: vec![last_internal], public_values: user_public_values, }; - let root_proof = SingleSegmentVmProver::prove( + + SingleSegmentVmProver::prove( &mut self.root_prover, root_input.write(), VM_MAX_TRACE_HEIGHTS, ) - .expect("root proof generation should pass"); - - root_proof + .expect("root proof generation should pass") } } @@ -852,7 +845,7 @@ mod tests { } #[test] - // #[ignore = "need to generate proof first"] + #[ignore = "need to generate proof first"] pub fn test_aggregation() { let stack_size = 256 * 1024 * 1024; // 64 MB diff --git a/ceno_recursion/src/aggregation/root.rs b/ceno_recursion/src/aggregation/root.rs index c2bbb9fc0..ddc0e9b1c 100644 --- a/ceno_recursion/src/aggregation/root.rs +++ b/ceno_recursion/src/aggregation/root.rs @@ -5,13 +5,11 @@ // builder.assert_usize_eq(is_sum_x_zero, Usize::from(1)); // builder.assert_usize_eq(is_sum_y_zero, Usize::from(1)); -use openvm_continuations::{ - C, F, SC, -}; +use openvm_continuations::{C, F, SC}; use openvm_instructions::program::Program; use openvm_native_compiler::{ conversion::CompilerOptions, - ir::{Array, Builder, Config, DIGEST_SIZE, Felt, RVar, Variable}, + ir::{Array, Builder, Config, DIGEST_SIZE, Felt, Variable}, }; use openvm_native_recursion::{ fri::TwoAdicFriPcsVariable, hints::Hintable, types::new_from_inner_multi_vk, @@ -19,23 +17,16 @@ use openvm_native_recursion::{ }; use openvm_stark_backend::keygen::types::MultiStarkVerifyingKey; use openvm_stark_sdk::config::FriParameters; -use std::{array, borrow::Borrow}; - -use openvm_stark_sdk::{ - config::baby_bear_poseidon2::BabyBearPoseidon2Config, - openvm_stark_backend::{ - config::{Com, StarkGenericConfig, Val}, - p3_field::PrimeField32, - proof::Proof, - }, -}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use openvm_native_compiler::{ - asm::AsmConfig, - prelude::*, -}; -use openvm_native_recursion::vars::StarkProofVariable; +use std::array; + use crate::aggregation::internal::NonLeafVerifierVariables; +use openvm_native_compiler::prelude::*; +use openvm_native_recursion::vars::StarkProofVariable; +use openvm_stark_sdk::openvm_stark_backend::{ + config::{StarkGenericConfig, Val}, + proof::Proof, +}; +use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] pub struct CenoRootVmVerifierInput { @@ -130,7 +121,7 @@ impl CenoRootVmVerifierConfig { internal_pcs, internal_advice, }; - let (merged_pvs, expected_leaf_commit) = + let (_merged_pvs, _expected_leaf_commit) = non_leaf_verifier.verify_internal_or_leaf_verifier_proofs(&mut builder, &proofs); builder.cycle_tracker_end("VerifyProofs"); From 01c5c43e4d27facb4e06c53fb48d14d68bb9e0b3 Mon Sep 17 00:00:00 2001 From: Ray Gao Date: Thu, 11 Dec 2025 14:23:10 -0500 Subject: [PATCH 05/14] clippy --- ceno_recursion/src/aggregation/mod.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/ceno_recursion/src/aggregation/mod.rs b/ceno_recursion/src/aggregation/mod.rs index 76ac07200..729b774a5 100644 --- a/ceno_recursion/src/aggregation/mod.rs +++ b/ceno_recursion/src/aggregation/mod.rs @@ -767,7 +767,6 @@ pub fn verify_proofs( #[cfg(test)] mod tests { - use super::verify_e2e_stark_proof; use crate::{ aggregation::{CenoAggregationProver, verify_proofs}, zkvm_verifier::binding::E, @@ -778,8 +777,7 @@ mod tests { structs::ZKVMVerifyingKey, }; use mpcs::{Basefold, BasefoldRSParams}; - use openvm_stark_sdk::{config::setup_tracing_with_log_level, p3_bn254_fr::Bn254Fr}; - use p3::field::FieldAlgebra; + use openvm_stark_sdk::config::setup_tracing_with_log_level; use std::fs::File; pub fn aggregation_inner_thread() { @@ -797,9 +795,9 @@ mod tests { .expect("Failed to deserialize vk file"); let mut agg_prover = CenoAggregationProver::from_base_vk(vk); - let root_proof = agg_prover.generate_root_proof(zkvm_proofs); + let _root_proof = agg_prover.generate_root_proof(zkvm_proofs); - // _debug + // _todo: add a verification step after root proof generation // verify_e2e_stark_proof( // &agg_prover.vk, // &root_stark_proof, From 6c4440acf49da12bf3a15792d16d02f9d845e47e Mon Sep 17 00:00:00 2001 From: Ray Gao Date: Mon, 15 Dec 2025 18:27:58 -0500 Subject: [PATCH 06/14] add init_pc to ceno root pv --- ceno_recursion/src/aggregation/root.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ceno_recursion/src/aggregation/root.rs b/ceno_recursion/src/aggregation/root.rs index ddc0e9b1c..65af0de40 100644 --- a/ceno_recursion/src/aggregation/root.rs +++ b/ceno_recursion/src/aggregation/root.rs @@ -73,11 +73,13 @@ pub struct CenoRootVmVerifierConfig { #[derive(Debug)] pub struct CenoRootVmVerifierPvs { + pub init_pc: T, pub public_values: Vec, } impl CenoRootVmVerifierPvs { pub fn flatten(self) -> Vec { let mut ret = vec![]; + ret.extend(vec![self.init_pc]); ret.extend(self.public_values); ret } @@ -121,7 +123,7 @@ impl CenoRootVmVerifierConfig { internal_pcs, internal_advice, }; - let (_merged_pvs, _expected_leaf_commit) = + let (merged_pvs, _expected_leaf_commit) = non_leaf_verifier.verify_internal_or_leaf_verifier_proofs(&mut builder, &proofs); builder.cycle_tracker_end("VerifyProofs"); @@ -140,6 +142,7 @@ impl CenoRootVmVerifierConfig { builder.cycle_tracker_end("ExtractPublicValues"); CenoRootVmVerifierPvs { + init_pc: merged_pvs.connector.initial_pc, public_values: public_values_vec, } }; From 712f4264a9c23a5f9f5ca7cb394d219f42653714 Mon Sep 17 00:00:00 2001 From: Ray Gao Date: Mon, 15 Dec 2025 19:54:01 -0500 Subject: [PATCH 07/14] correct root pv len --- ceno_recursion/src/aggregation/mod.rs | 2 +- ceno_recursion/src/aggregation/root.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ceno_recursion/src/aggregation/mod.rs b/ceno_recursion/src/aggregation/mod.rs index 729b774a5..a91235298 100644 --- a/ceno_recursion/src/aggregation/mod.rs +++ b/ceno_recursion/src/aggregation/mod.rs @@ -91,7 +91,7 @@ pub const LEAF_LOG_BLOWUP: usize = 1; pub const INTERNAL_LOG_BLOWUP: usize = 2; pub const ROOT_LOG_BLOWUP: usize = 3; pub const ROOT_MAX_CONSTRAINT_DEG: usize = (1 << ROOT_LOG_BLOWUP) + 1; -pub const ROOT_NUM_PUBLIC_VALUES: usize = 14; +pub const ROOT_NUM_PUBLIC_VALUES: usize = 15; pub const SBOX_SIZE: usize = 7; const VM_MAX_TRACE_HEIGHTS: &[u32] = &[ 4194304, 4, 128, 2097152, 8388608, 4194304, 262144, 8388608, 16777216, 2097152, 16777216, diff --git a/ceno_recursion/src/aggregation/root.rs b/ceno_recursion/src/aggregation/root.rs index 65af0de40..8f7917e23 100644 --- a/ceno_recursion/src/aggregation/root.rs +++ b/ceno_recursion/src/aggregation/root.rs @@ -136,7 +136,7 @@ impl CenoRootVmVerifierConfig { builder.cycle_tracker_start("ExtractPublicValues"); // builder.assert_usize_eq(public_values.len(), RVar::from(self.num_user_public_values)); - let public_values_vec: Vec> = (0..self.num_user_public_values) + let public_values_vec: Vec> = (0..(self.num_user_public_values - 1)) // The init_pc is read separately .map(|i| builder.get(&public_values, i)) .collect(); builder.cycle_tracker_end("ExtractPublicValues"); From 723433c3c3bd38e2a7168dc35d18dec3ee8d3401 Mon Sep 17 00:00:00 2001 From: Ray Gao Date: Fri, 19 Dec 2025 07:36:16 -0500 Subject: [PATCH 08/14] New Recursion PV + EC Sum Aggregation/Check (Internal & Root) (#1196) - Restructure the public values in recursion - Aggregate and check septic point summation in the aggregation tree --- ceno_recursion/src/aggregation/internal.rs | 165 +++++++------ ceno_recursion/src/aggregation/mod.rs | 244 +++++++++---------- ceno_recursion/src/aggregation/root.rs | 19 +- ceno_recursion/src/aggregation/types.rs | 149 ++++++++--- ceno_recursion/src/zkvm_verifier/binding.rs | 23 +- ceno_recursion/src/zkvm_verifier/verifier.rs | 35 +-- ceno_zkvm/src/scheme/verifier.rs | 6 - 7 files changed, 365 insertions(+), 276 deletions(-) diff --git a/ceno_recursion/src/aggregation/internal.rs b/ceno_recursion/src/aggregation/internal.rs index bd494a0e9..0d57cdd7b 100644 --- a/ceno_recursion/src/aggregation/internal.rs +++ b/ceno_recursion/src/aggregation/internal.rs @@ -3,12 +3,13 @@ /// https://github.com/openvm-org/openvm/blob/main/crates/continuations/src/verifier/common/non_leaf.rs use std::{array, borrow::Borrow}; +use ceno_zkvm::scheme::constants::SEPTIC_EXTENSION_DEGREE; use openvm_circuit::arch::PUBLIC_VALUES_AIR_ID; use openvm_instructions::program::Program; use openvm_native_compiler::{ conversion::CompilerOptions, - ir::{Array, Builder, Config, DIGEST_SIZE, Felt, RVar}, - prelude::Var, + ir::{Array, Builder, Config, DIGEST_SIZE, Felt, RVar, Usize}, + prelude::*, }; use openvm_native_recursion::{ challenger::duplex::DuplexChallengerVariable, fri::TwoAdicFriPcsVariable, stark::StarkVerifier, @@ -21,15 +22,22 @@ use openvm_stark_sdk::{ }; use p3::field::FieldAlgebra; +use crate::{ + aggregation::{ + InternalVmVerifierInput, + types::{InternalVmVerifierExtraPvs, InternalVmVerifierPvs, VmVerifierPvs}, + }, + zkvm_verifier::{ + binding::{SepticExtensionVariable, SepticPointVariable}, + verifier::add_septic_points_in_place, + }, +}; use openvm_continuations::verifier::{ common::{ assert_or_assign_connector_pvs, assert_required_air_for_agg_vm_present, - assert_single_segment_vm_exit_successfully, get_program_commit, types::VmVerifierPvs, - }, - internal::{ - types::{InternalVmVerifierExtraPvs, InternalVmVerifierInput, InternalVmVerifierPvs}, - vars::InternalVmVerifierInputVariable, + assert_single_segment_vm_exit_successfully, get_program_commit, }, + internal::vars::InternalVmVerifierInputVariable, }; use openvm_native_recursion::hints::Hintable; @@ -91,6 +99,16 @@ impl NonLeafVerifierVariables { let pvs = VmVerifierPvs::>::uninit(builder); let leaf_verifier_commit = array::from_fn(|_| builder.uninit()); + let ec_sum = SepticPointVariable { + x: SepticExtensionVariable { + vs: builder.dyn_array(7), + }, + y: SepticExtensionVariable { + vs: builder.dyn_array(7), + }, + is_infinity: Usize::uninit(builder), + }; + builder.range(0, proofs.len()).for_each(|i_vec, builder| { let i = i_vec[0]; let proof = builder.get(proofs, i); @@ -98,26 +116,61 @@ impl NonLeafVerifierVariables { let proof_vm_pvs = self.verify_internal_or_leaf_verifier_proof(builder, &proof); assert_single_segment_vm_exit_successfully(builder, &proof); - builder.if_eq(i, RVar::zero()).then_or_else( |builder| { - builder.assign(&pvs.app_commit, proof_vm_pvs.vm_verifier_pvs.app_commit); + for i in 0..SEPTIC_EXTENSION_DEGREE { + let x = Ext::uninit(builder); + builder.assign(&x, proof_vm_pvs.vm_verifier_pvs.shard_ram_connector.x[i]); + let y = Ext::uninit(builder); + builder.assign(&y, proof_vm_pvs.vm_verifier_pvs.shard_ram_connector.y[i]); + + builder.set(&ec_sum.x.vs, i, x); + builder.set(&ec_sum.y.vs, i, y); + } + let inf = Usize::Var(builder.cast_felt_to_var( + proof_vm_pvs.vm_verifier_pvs.shard_ram_connector.is_infinity, + )); + builder.assign(&ec_sum.is_infinity, inf); + builder.assign( &leaf_verifier_commit, proof_vm_pvs.extra_pvs.leaf_verifier_commit, ); }, |builder| { - builder.assert_eq::<[_; DIGEST_SIZE]>( - pvs.app_commit, - proof_vm_pvs.vm_verifier_pvs.app_commit, - ); + let right = SepticPointVariable { + x: SepticExtensionVariable { + vs: builder.dyn_array(7), + }, + y: SepticExtensionVariable { + vs: builder.dyn_array(7), + }, + is_infinity: Usize::uninit(builder), + }; + + for i in 0..SEPTIC_EXTENSION_DEGREE { + let x = Ext::uninit(builder); + builder.assign(&x, proof_vm_pvs.vm_verifier_pvs.shard_ram_connector.x[i]); + let y = Ext::uninit(builder); + builder.assign(&y, proof_vm_pvs.vm_verifier_pvs.shard_ram_connector.y[i]); + + builder.set(&right.x.vs, i, x); + builder.set(&right.y.vs, i, y); + } + let inf = Usize::Var(builder.cast_felt_to_var( + proof_vm_pvs.vm_verifier_pvs.shard_ram_connector.is_infinity, + )); + builder.assign(&right.is_infinity, inf); + + add_septic_points_in_place(builder, &ec_sum, &right); + builder.assert_eq::<[_; DIGEST_SIZE]>( leaf_verifier_commit, proof_vm_pvs.extra_pvs.leaf_verifier_commit, ); }, ); + assert_or_assign_connector_pvs( builder, &pvs.connector, @@ -125,72 +178,27 @@ impl NonLeafVerifierVariables { &proof_vm_pvs.vm_verifier_pvs.connector, ); - // TODO: sum shard ram ec point in each proof - // // EC sum verification - // let expected_last_shard_id = Usize::uninit(builder); - // builder.assign(&expected_last_shard_id, pv.len() - Usize::from(1)); - - // let shard_id_fs = builder.get(&shard_raw_pi, SHARD_ID_IDX); - // let shard_id_f = builder.get(&shard_id_fs, 0); - // let shard_id = Usize::Var(builder.cast_felt_to_var(shard_id_f)); - // builder.assert_usize_eq(expected_last_shard_id, shard_id); + for i in 0..SEPTIC_EXTENSION_DEGREE { + let x_ext = builder.get(&ec_sum.x.vs, i); + let y_ext = builder.get(&ec_sum.y.vs, i); + let x_fs = builder.ext2felt(x_ext); + let y_fs = builder.ext2felt(y_ext); + let x = builder.get(&x_fs, 0); + let y = builder.get(&y_fs, 0); - // let ec_sum = SepticPointVariable { - // x: SepticExtensionVariable { - // vs: builder.dyn_array(7), - // }, - // y: SepticExtensionVariable { - // vs: builder.dyn_array(7), - // }, - // is_infinity: Usize::uninit(builder), - // }; - // builder.assign(&ec_sum.is_infinity, Usize::from(1)); - - // builder.range(0, pv.len()).for_each(|idx_vec, builder| { - // let shard_pv = builder.get(&pv, idx_vec[0]); - // let x = SepticExtensionVariable { - // vs: shard_pv.slice( - // builder, - // SHARD_RW_SUM_IDX, - // SHARD_RW_SUM_IDX + SEPTIC_EXTENSION_DEGREE, - // ), - // }; - // let y = SepticExtensionVariable { - // vs: shard_pv.slice( - // builder, - // SHARD_RW_SUM_IDX + SEPTIC_EXTENSION_DEGREE, - // SHARD_RW_SUM_IDX + 2 * SEPTIC_EXTENSION_DEGREE, - // ), - // }; - // let shard_ec = SepticPointVariable { - // x: x.clone(), - // y: y.clone(), - // is_infinity: Usize::uninit(builder), - // }; - // let is_x_zero = x.is_zero(builder); - // let is_y_zero = y.is_zero(builder); - // builder.if_eq(is_x_zero, Usize::from(1)).then_or_else( - // |builder| { - // builder - // .if_eq(is_y_zero.clone(), Usize::from(1)) - // .then_or_else( - // |builder| { - // builder.assign(&shard_ec.is_infinity, Usize::from(1)); - // }, - // |builder| { - // builder.assign(&shard_ec.is_infinity, Usize::from(0)); - // }, - // ); - // }, - // |builder| { - // builder.assign(&shard_ec.is_infinity, Usize::from(0)); - // }, - // ); - - // add_septic_points_in_place(builder, &ec_sum, &shard_ec); - // }); - - // add_septic_points_in_place(builder, &ec_sum, &calculated_shard_ec_sum); + builder.assign(&pvs.shard_ram_connector.x[i], x); + builder.assign(&pvs.shard_ram_connector.y[i], y); + } + builder + .if_eq(ec_sum.is_infinity.clone(), Usize::from(1)) + .then_or_else( + |builder| { + builder.assign(&pvs.shard_ram_connector.is_infinity, C::F::ONE); + }, + |builder| { + builder.assign(&pvs.shard_ram_connector.is_infinity, C::F::ZERO); + }, + ); // This is only needed when `is_terminate` but branching here won't save much, so we // always assign it. @@ -301,6 +309,7 @@ impl InternalVmVerifierConfig { internal_pcs, internal_advice, }; + builder.cycle_tracker_start("VerifyProofs"); let (vm_verifier_pvs, leaf_verifier_commit) = non_leaf_verifier.verify_internal_or_leaf_verifier_proofs(&mut builder, &proofs); diff --git a/ceno_recursion/src/aggregation/mod.rs b/ceno_recursion/src/aggregation/mod.rs index a91235298..f498ab0de 100644 --- a/ceno_recursion/src/aggregation/mod.rs +++ b/ceno_recursion/src/aggregation/mod.rs @@ -7,7 +7,7 @@ use crate::{ }; use ceno_zkvm::{ instructions::riscv::constants::{END_PC_IDX, EXIT_CODE_IDX, INIT_PC_IDX}, - scheme::ZKVMProof, + scheme::{ZKVMProof, constants::SEPTIC_EXTENSION_DEGREE}, structs::ZKVMVerifyingKey, }; use ff_ext::BabyBearExt4; @@ -24,11 +24,7 @@ use openvm_stark_backend::config::{PcsProverData, Val}; use internal::InternalVmVerifierConfig; use openvm_continuations::{ C, - verifier::{ - common::types::VmVerifierPvs, - internal::types::{InternalVmVerifierInput, InternalVmVerifierPvs, VmStarkProof}, - root::types::RootVmVerifierInput, - }, + verifier::{internal::types::InternalVmVerifierInput, root::types::RootVmVerifierInput}, }; #[cfg(feature = "gpu")] use openvm_cuda_backend::engine::GpuBabyBearPoseidon2Engine as BabyBearPoseidon2Engine; @@ -41,7 +37,6 @@ use openvm_native_compiler::{ use openvm_native_recursion::hints::Hintable; use openvm_sdk::{ SC, - commit::AppExecutionCommit, config::DEFAULT_NUM_CHILDREN_INTERNAL, prover::vm::{new_local_prover, types::VmProvingKey}, }; @@ -58,21 +53,13 @@ use openvm_stark_sdk::{ }, engine::StarkFriEngine, openvm_stark_backend::keygen::types::MultiStarkVerifyingKey, - p3_bn254_fr::Bn254Fr, }; use p3::field::FieldAlgebra; use serde::{Deserialize, Serialize}; -use std::{borrow::Borrow, sync::Arc, time::Instant}; +use std::{sync::Arc, time::Instant}; pub type RecPcs = Basefold; -use openvm_circuit::{ - arch::{ - CONNECTOR_AIR_ID, PROGRAM_AIR_ID, PROGRAM_CACHED_TRACE_INDEX, PUBLIC_VALUES_AIR_ID, - SingleSegmentVmProver, - hasher::{Hasher, poseidon2::vm_poseidon2_hasher}, - instructions::exe::VmExe, - }, - system::{memory::CHUNK, program::trace::compute_exe_commit}, -}; +use crate::aggregation::types::{InternalVmVerifierPvs, VmVerifierPvs}; +use openvm_circuit::arch::{PUBLIC_VALUES_AIR_ID, SingleSegmentVmProver, instructions::exe::VmExe}; use openvm_continuations::RootSC; use openvm_native_compiler::{ asm::AsmConfig, @@ -448,16 +435,10 @@ impl CenoLeafVmVerifierConfig { builder.cycle_tracker_start("Verify Ceno ZKVM Proof"); let zkvm_proof = ceno_leaf_input.proof; let raw_pi = zkvm_proof.raw_pi.clone(); - let _calculated_shard_ec_sum = verify_zkvm_proof(&mut builder, zkvm_proof, &self.vk); + let shard_ec_sum = verify_zkvm_proof(&mut builder, zkvm_proof, &self.vk); builder.cycle_tracker_end("Verify Ceno ZKVM Proof"); builder.cycle_tracker_start("PV Operations"); - - // TODO: define our own VmVerifierPvs - for i in 0..DIGEST_SIZE { - builder.assign(&stark_pvs.app_commit[i], F::ZERO); - } - let pv = &raw_pi; let init_pc = { let arr = builder.get(pv, INIT_PC_IDX); @@ -475,7 +456,27 @@ impl CenoLeafVmVerifierConfig { builder.assign(&stark_pvs.connector.final_pc, end_pc); builder.assign(&stark_pvs.connector.exit_code, exit_code); - // TODO: assign shard_ec_sum to stark_pvs.shard_ec_sum + for i in 0..SEPTIC_EXTENSION_DEGREE { + let x_ext = builder.get(&shard_ec_sum.x.vs, i); + let y_ext = builder.get(&shard_ec_sum.y.vs, i); + let x_fs = builder.ext2felt(x_ext); + let y_fs = builder.ext2felt(y_ext); + let x = builder.get(&x_fs, 0); + let y = builder.get(&y_fs, 0); + + builder.assign(&stark_pvs.shard_ram_connector.x[i], x); + builder.assign(&stark_pvs.shard_ram_connector.y[i], y); + } + builder + .if_eq(shard_ec_sum.is_infinity, Usize::from(1)) + .then_or_else( + |builder| { + builder.assign(&stark_pvs.shard_ram_connector.is_infinity, F::ONE); + }, + |builder| { + builder.assign(&stark_pvs.shard_ram_connector.is_infinity, F::ZERO); + }, + ); // builder // .if_eq(ceno_leaf_input.is_last, Usize::from(1)) @@ -504,7 +505,6 @@ impl CenoLeafVmVerifierConfig { // ); // builder.assign(&prev_pc, end_pc); // }); - // }); for pv in stark_pvs.flatten() { @@ -613,101 +613,101 @@ pub(crate) fn chunk_ceno_leaf_proof_inputs( // Source from OpenVm SDK::verify_e2e_stark_proof with abridged key // See: https://github.com/openvm-org/openvm -pub fn verify_e2e_stark_proof( - k: &CenoRecursionVerifierKeys, - proof: &VmStarkProof, - _expected_exe_commit: &Bn254Fr, - _expected_vm_commit: &Bn254Fr, -) -> Result { - if proof.inner.per_air.len() < 3 { - return Err("Invalid number of AIRs: expected at least 3".into()); - } else if proof.inner.per_air[0].air_id != PROGRAM_AIR_ID { - return Err("Missing program AIR".into()); - } else if proof.inner.per_air[1].air_id != CONNECTOR_AIR_ID { - return Err("Missing connector AIR".into()); - } else if proof.inner.per_air[2].air_id != PUBLIC_VALUES_AIR_ID { - return Err("Missing public values AIR".into()); - } - let public_values_air_proof_data = &proof.inner.per_air[2]; - - let program_commit = proof.inner.commitments.main_trace[PROGRAM_CACHED_TRACE_INDEX].as_ref(); - let internal_commit: &[_; CHUNK] = &k.internal_commit.into(); - - let (vm_vk, fri_params, vm_commit) = if program_commit == internal_commit { - let internal_pvs: &InternalVmVerifierPvs<_> = public_values_air_proof_data - .public_values - .as_slice() - .borrow(); - if internal_commit != &internal_pvs.extra_pvs.internal_program_commit { - return Err(format!( - "Invalid internal program commit: expected {:?}, got {:?}", - internal_commit, internal_pvs.extra_pvs.internal_program_commit - )); - } - ( - &k.internal_vm_vk, - k.internal_fri_params, - internal_pvs.extra_pvs.leaf_verifier_commit, - ) - } else { - (&k.leaf_vm_vk, k.leaf_fri_params, *program_commit) - }; - let e = BabyBearPoseidon2Engine::new(fri_params); - e.verify(vm_vk, &proof.inner) - .expect("stark e2e proof verification should pass"); - - let pvs: &VmVerifierPvs<_> = - public_values_air_proof_data.public_values[..VmVerifierPvs::::width()].borrow(); - - // _debug: AIR ordering - // if let Some(exit_code) = pvs.connector.exit_code() { - // if exit_code != 0 { - // return Err(format!( - // "Invalid exit code: expected 0, got {}", - // exit_code - // )); - // } - // } else { - // return Err(format!("Program did not terminate")); - // } - - let hasher = vm_poseidon2_hasher(); - let _public_values_root = hasher.merkle_root(&proof.user_public_values); - // _debug: Public value commitment - // if public_values_root != pvs.public_values_commit { - // return Err(format!( - // "Invalid public values root: expected {:?}, got {:?}", - // pvs.public_values_commit, - // public_values_root - // )); - // } - - let exe_commit = compute_exe_commit( - &hasher, - &pvs.app_commit, - &pvs.memory.initial_root, - pvs.connector.initial_pc, - ); - let app_commit = AppExecutionCommit::from_field_commit(exe_commit, vm_commit); - let _exe_commit_bn254 = app_commit.app_exe_commit.to_bn254(); - let _vm_commit_bn254 = app_commit.app_vm_commit.to_bn254(); - - // _debug: execution commit checks - // if exe_commit_bn254 != *expected_exe_commit { - // return Err(eyre::eyre!( - // "Invalid app exe commit: expected {:?}, got {:?}", - // expected_exe_commit, - // exe_commit_bn254 - // )); - // } else if vm_commit_bn254 != *expected_vm_commit { - // return Err(eyre::eyre!( - // "Invalid app vm commit: expected {:?}, got {:?}", - // expected_vm_commit, - // vm_commit_bn254 - // )); - // } - Ok(app_commit) -} +// pub fn verify_e2e_stark_proof( +// k: &CenoRecursionVerifierKeys, +// proof: &VmStarkProof, +// _expected_exe_commit: &Bn254Fr, +// _expected_vm_commit: &Bn254Fr, +// ) -> Result { +// if proof.inner.per_air.len() < 3 { +// return Err("Invalid number of AIRs: expected at least 3".into()); +// } else if proof.inner.per_air[0].air_id != PROGRAM_AIR_ID { +// return Err("Missing program AIR".into()); +// } else if proof.inner.per_air[1].air_id != CONNECTOR_AIR_ID { +// return Err("Missing connector AIR".into()); +// } else if proof.inner.per_air[2].air_id != PUBLIC_VALUES_AIR_ID { +// return Err("Missing public values AIR".into()); +// } +// let public_values_air_proof_data = &proof.inner.per_air[2]; +// +// let program_commit = proof.inner.commitments.main_trace[PROGRAM_CACHED_TRACE_INDEX].as_ref(); +// let internal_commit: &[_; CHUNK] = &k.internal_commit.into(); +// +// let (vm_vk, fri_params, vm_commit) = if program_commit == internal_commit { +// let internal_pvs: &InternalVmVerifierPvs<_> = public_values_air_proof_data +// .public_values +// .as_slice() +// .borrow(); +// if internal_commit != &internal_pvs.extra_pvs.internal_program_commit { +// return Err(format!( +// "Invalid internal program commit: expected {:?}, got {:?}", +// internal_commit, internal_pvs.extra_pvs.internal_program_commit +// )); +// } +// ( +// &k.internal_vm_vk, +// k.internal_fri_params, +// internal_pvs.extra_pvs.leaf_verifier_commit, +// ) +// } else { +// (&k.leaf_vm_vk, k.leaf_fri_params, *program_commit) +// }; +// let e = BabyBearPoseidon2Engine::new(fri_params); +// e.verify(vm_vk, &proof.inner) +// .expect("stark e2e proof verification should pass"); +// +// let pvs: &VmVerifierPvs<_> = +// public_values_air_proof_data.public_values[..VmVerifierPvs::::width()].borrow(); +// +// _debug: AIR ordering +// if let Some(exit_code) = pvs.connector.exit_code() { +// if exit_code != 0 { +// return Err(format!( +// "Invalid exit code: expected 0, got {}", +// exit_code +// )); +// } +// } else { +// return Err(format!("Program did not terminate")); +// } +// +// let hasher = vm_poseidon2_hasher(); +// let _public_values_root = hasher.merkle_root(&proof.user_public_values); +// _debug: Public value commitment +// if public_values_root != pvs.public_values_commit { +// return Err(format!( +// "Invalid public values root: expected {:?}, got {:?}", +// pvs.public_values_commit, +// public_values_root +// )); +// } +// +// let exe_commit = compute_exe_commit( +// &hasher, +// &pvs.app_commit, +// &pvs.memory.initial_root, +// pvs.connector.initial_pc, +// ); +// let app_commit = AppExecutionCommit::from_field_commit(exe_commit, vm_commit); +// let _exe_commit_bn254 = app_commit.app_exe_commit.to_bn254(); +// let _vm_commit_bn254 = app_commit.app_vm_commit.to_bn254(); +// +// _debug: execution commit checks +// if exe_commit_bn254 != *expected_exe_commit { +// return Err(eyre::eyre!( +// "Invalid app exe commit: expected {:?}, got {:?}", +// expected_exe_commit, +// exe_commit_bn254 +// )); +// } else if vm_commit_bn254 != *expected_vm_commit { +// return Err(eyre::eyre!( +// "Invalid app vm commit: expected {:?}, got {:?}", +// expected_vm_commit, +// vm_commit_bn254 +// )); +// } +// Ok(app_commit) +// } /// Build Ceno's zkVM verifier program from vk in OpenVM's eDSL pub fn build_zkvm_verifier_program( diff --git a/ceno_recursion/src/aggregation/root.rs b/ceno_recursion/src/aggregation/root.rs index 8f7917e23..cc93532cd 100644 --- a/ceno_recursion/src/aggregation/root.rs +++ b/ceno_recursion/src/aggregation/root.rs @@ -1,10 +1,3 @@ -// TODO: assert that the shard ram ec point is `PointAtInfinity` - -// let is_sum_x_zero = ec_sum.x.is_zero(builder); -// let is_sum_y_zero = ec_sum.y.is_zero(builder); -// builder.assert_usize_eq(is_sum_x_zero, Usize::from(1)); -// builder.assert_usize_eq(is_sum_y_zero, Usize::from(1)); - use openvm_continuations::{C, F, SC}; use openvm_instructions::program::Program; use openvm_native_compiler::{ @@ -19,13 +12,14 @@ use openvm_stark_backend::keygen::types::MultiStarkVerifyingKey; use openvm_stark_sdk::config::FriParameters; use std::array; -use crate::aggregation::internal::NonLeafVerifierVariables; +use crate::aggregation::{SEPTIC_EXTENSION_DEGREE, internal::NonLeafVerifierVariables}; use openvm_native_compiler::prelude::*; use openvm_native_recursion::vars::StarkProofVariable; use openvm_stark_sdk::openvm_stark_backend::{ config::{StarkGenericConfig, Val}, proof::Proof, }; +use p3::field::FieldAlgebra; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] @@ -127,6 +121,15 @@ impl CenoRootVmVerifierConfig { non_leaf_verifier.verify_internal_or_leaf_verifier_proofs(&mut builder, &proofs); builder.cycle_tracker_end("VerifyProofs"); + // Assert final ec sum is zero + let z: Felt<_> = builder.constant(::F::ZERO); + for i in 0..SEPTIC_EXTENSION_DEGREE { + builder.assert_felt_eq(merged_pvs.shard_ram_connector.x[i], z); + builder.assert_felt_eq(merged_pvs.shard_ram_connector.y[i], z); + } + let one: Felt<_> = builder.constant(::F::ONE); + builder.assert_felt_eq(merged_pvs.shard_ram_connector.is_infinity, one); + /* _todo: Change merged pv operations, including checking final ec sum is infinity // App Program should terminate builder.assert_felt_eq(merged_pvs.connector.is_terminate, F::ONE); diff --git a/ceno_recursion/src/aggregation/types.rs b/ceno_recursion/src/aggregation/types.rs index be88a6fb8..cf244aec2 100644 --- a/ceno_recursion/src/aggregation/types.rs +++ b/ceno_recursion/src/aggregation/types.rs @@ -1,38 +1,111 @@ -// TODO: enable this -// #[derive(Debug, Clone, AlignedBorrow)] -// pub struct ContinuationPvs { -// pub sum: SepticPoint, -// } - -// impl ContinuationPvs> { -// pub fn uninit(builder: &mut Builder) -> Self { -// todo!() -// } -// } - -// #[derive(Debug, Clone, AlignedBorrow)] -// #[repr(C)] -// pub struct VmVerifierPvs { -// /// The merged execution state of all the segments this circuit aggregates. -// pub connector: VmConnectorPvs, -// /// The state before/after all the segments this circuit aggregates. -// // (TODO) pub shard_ram_connector: ContinuationPvs, -// /// The merkle root of all public values. This is only meaningful when the last segment is -// /// aggregated by this circuit. -// pub public_values_commit: [T; DIGEST_SIZE], -// } - -// impl VmVerifierPvs> { -// pub fn uninit(builder: &mut Builder) -> Self { -// VmVerifierPvs { -// connector: VmConnectorPvs { -// initial_pc: builder.uninit(), -// final_pc: builder.uninit(), -// exit_code: builder.uninit(), -// is_terminate: builder.uninit(), -// }, -// // shard_ram_connector: builder.uninit(), -// public_values_commit: array::from_fn(|_| builder.uninit()), -// } -// } -// } +use ceno_zkvm::scheme::constants::SEPTIC_EXTENSION_DEGREE; +use openvm_circuit::{circuit_derive::AlignedBorrow, system::connector::VmConnectorPvs}; +use openvm_native_compiler::ir::{Builder, Config, DIGEST_SIZE, Felt, Variable}; +use p3::field::PrimeField32; +use std::{array, borrow::BorrowMut}; + +#[derive(Clone, Copy, AlignedBorrow)] +pub struct ContinuationPvs { + pub x: [T; SEPTIC_EXTENSION_DEGREE], + pub y: [T; SEPTIC_EXTENSION_DEGREE], + pub is_infinity: T, +} + +impl ContinuationPvs> { + pub fn uninit>(builder: &mut Builder) -> Self { + Self { + x: array::from_fn(|_| builder.uninit()), + y: array::from_fn(|_| builder.uninit()), + is_infinity: Felt::uninit(builder), + } + } +} + +#[derive(Clone, Copy, AlignedBorrow)] +#[repr(C)] +pub struct VmVerifierPvs { + /// The merged execution state of all the segments this circuit aggregates. + pub connector: VmConnectorPvs, + /// The state before/after all the segments this circuit aggregates. + pub shard_ram_connector: ContinuationPvs, + /// The merkle root of all public values. This is only meaningful when the last segment is + /// aggregated by this circuit. + pub public_values_commit: [T; DIGEST_SIZE], +} + +impl VmVerifierPvs> { + pub fn uninit>(builder: &mut Builder) -> Self { + VmVerifierPvs { + connector: VmConnectorPvs { + initial_pc: builder.uninit(), + final_pc: builder.uninit(), + exit_code: builder.uninit(), + is_terminate: builder.uninit(), + }, + shard_ram_connector: ContinuationPvs::uninit(builder), + public_values_commit: array::from_fn(|_| builder.uninit()), + } + } +} + +impl VmVerifierPvs> { + pub fn flatten(self) -> Vec> { + let mut v = vec![Felt(0, Default::default()); VmVerifierPvs::::width()]; + *v.as_mut_slice().borrow_mut() = self; + v + } +} + +/// Aggregated state of all segments +#[derive(Clone, Copy, AlignedBorrow)] +#[repr(C)] +pub struct InternalVmVerifierPvs { + pub vm_verifier_pvs: VmVerifierPvs, + pub extra_pvs: InternalVmVerifierExtraPvs, +} + +/// Extra PVs for internal VM verifier except VmVerifierPvs. +#[derive(Clone, Copy, AlignedBorrow)] +#[repr(C)] +pub struct InternalVmVerifierExtraPvs { + /// The commitment of the leaf verifier program. + pub leaf_verifier_commit: [T; DIGEST_SIZE], + /// For recursion verification, a program need its own commitment, but its own commitment + /// cannot be hardcoded inside the program itself. So the commitment has to be read from + /// external and be committed. + pub internal_program_commit: [T; DIGEST_SIZE], +} + +impl InternalVmVerifierPvs> { + pub fn uninit>(builder: &mut Builder) -> Self { + Self { + vm_verifier_pvs: VmVerifierPvs::>::uninit(builder), + extra_pvs: InternalVmVerifierExtraPvs::>::uninit(builder), + } + } +} + +impl InternalVmVerifierPvs> { + pub fn flatten(self) -> Vec> { + let mut v = vec![Felt(0, Default::default()); InternalVmVerifierPvs::::width()]; + *v.as_mut_slice().borrow_mut() = self; + v + } +} + +impl InternalVmVerifierExtraPvs> { + pub fn uninit>(builder: &mut Builder) -> Self { + Self { + leaf_verifier_commit: array::from_fn(|_| builder.uninit()), + internal_program_commit: array::from_fn(|_| builder.uninit()), + } + } +} + +impl InternalVmVerifierExtraPvs> { + pub fn flatten(self) -> Vec> { + let mut v = vec![Felt(0, Default::default()); InternalVmVerifierExtraPvs::::width()]; + *v.as_mut_slice().borrow_mut() = self; + v + } +} diff --git a/ceno_recursion/src/zkvm_verifier/binding.rs b/ceno_recursion/src/zkvm_verifier/binding.rs index 50de33315..44d464641 100644 --- a/ceno_recursion/src/zkvm_verifier/binding.rs +++ b/ceno_recursion/src/zkvm_verifier/binding.rs @@ -11,7 +11,7 @@ use crate::{ }, }; use ceno_zkvm::{ - scheme::{ZKVMChipProof, ZKVMProof}, + scheme::{ZKVMChipProof, ZKVMProof, constants::SEPTIC_EXTENSION_DEGREE}, structs::{EccQuarkProof, TowerProofs}, }; use gkr_iop::gkr::{GKRProof, layer::sumcheck_layer::LayerProof}; @@ -800,6 +800,27 @@ pub struct SepticPointVariable { pub is_infinity: Usize, } +impl SepticPointVariable { + pub fn zero(builder: &mut Builder) -> Self { + let r = SepticPointVariable { + x: SepticExtensionVariable { + vs: builder.dyn_array(7), + }, + y: SepticExtensionVariable { + vs: builder.dyn_array(7), + }, + is_infinity: Usize::uninit(builder), + }; + let z: Ext = builder.constant(C::EF::ZERO); + for i in 0..SEPTIC_EXTENSION_DEGREE { + builder.set(&r.x.vs, i, z); + builder.set(&r.y.vs, i, z); + } + builder.assign(&r.is_infinity, Usize::from(1)); + r + } +} + pub struct EccQuarkProofInput { pub zerocheck_proof: IOPProof, pub num_instances: usize, diff --git a/ceno_recursion/src/zkvm_verifier/verifier.rs b/ceno_recursion/src/zkvm_verifier/verifier.rs index e521186f7..330417b0c 100644 --- a/ceno_recursion/src/zkvm_verifier/verifier.rs +++ b/ceno_recursion/src/zkvm_verifier/verifier.rs @@ -226,15 +226,7 @@ pub fn verify_zkvm_proof>( builder.dyn_array(zkvm_proof_input.chip_proofs.len()); let fixed_openings: Array> = builder.dyn_array(zkvm_proof_input.chip_proofs.len()); - let shard_ec_sum = SepticPointVariable { - x: SepticExtensionVariable { - vs: builder.dyn_array(7), - }, - y: SepticExtensionVariable { - vs: builder.dyn_array(7), - }, - is_infinity: Usize::uninit(builder), - }; + let shard_ec_sum = SepticPointVariable::zero(builder); let num_chips_verified: Usize = builder.eval(C::N::ZERO); let num_chips_have_fixed: Usize = builder.eval(C::N::ZERO); @@ -551,16 +543,7 @@ pub fn verify_chip_proof( &num_var_with_rotation, log2_num_instances.clone() + Usize::from(composed_cs.rotation_vars().unwrap_or(0)), ); - - let shard_ec_sum = SepticPointVariable { - x: SepticExtensionVariable { - vs: builder.dyn_array(7), - }, - y: SepticExtensionVariable { - vs: builder.dyn_array(7), - }, - is_infinity: Usize::uninit(builder), - }; + let shard_ec_sum = SepticPointVariable::zero(builder); if composed_cs.has_ecc_ops() { builder.assert_nonzero(&chip_proof.has_ecc_proof); @@ -1881,7 +1864,11 @@ pub fn septic_ext_squared( builder: &mut Builder, a: &SepticExtensionVariable, ) -> SepticExtensionVariable { + let z: Ext = builder.constant(C::EF::ZERO); let r: Array> = builder.dyn_array(SEPTIC_EXTENSION_DEGREE); + for i in 0..SEPTIC_EXTENSION_DEGREE { + builder.set(&r, i, z); + } let two_ext: Ext = builder.constant(C::EF::TWO); let five_ext: Ext = builder.constant(C::EF::from_canonical_u32(5)); @@ -1960,7 +1947,11 @@ pub fn septic_ext_mul( a: &SepticExtensionVariable, b: &SepticExtensionVariable, ) -> SepticExtensionVariable { + let z: Ext = builder.constant(C::EF::ZERO); let r: Array> = builder.dyn_array(SEPTIC_EXTENSION_DEGREE); + for i in 0..SEPTIC_EXTENSION_DEGREE { + builder.set(&r, i, z); + } let two_ext: Ext = builder.constant(C::EF::TWO); let five_ext: Ext = builder.constant(C::EF::from_canonical_u32(5)); @@ -2211,10 +2202,8 @@ pub fn add_septic_points_in_place( let y_sum = septic_ext_add(builder, &right.y, &left.y); let is_y_sum_zero = y_sum.is_zero(builder); builder.assert_usize_eq(is_y_sum_zero, Usize::from(1)); - let zero_ext_arr: Array> = - builder.dyn_array(SEPTIC_EXTENSION_DEGREE); - builder.assign(&left.x, zero_ext_arr.clone()); - builder.assign(&left.y, zero_ext_arr.clone()); + builder.assign(&left.x, y_sum.clone()); + builder.assign(&left.y, y_sum.clone()); builder.assign(&left.is_infinity, Usize::from(1)); }, ); diff --git a/ceno_zkvm/src/scheme/verifier.rs b/ceno_zkvm/src/scheme/verifier.rs index 94055853a..63aa545f5 100644 --- a/ceno_zkvm/src/scheme/verifier.rs +++ b/ceno_zkvm/src/scheme/verifier.rs @@ -122,14 +122,8 @@ impl> ZKVMVerifier let end_pc = vm_proof.pi_evals[END_PC_IDX]; // add to shard ec sum - // _debug - // println!("=> shard pi: {:?}", vm_proof.pi_evals.clone()); let shard_ec = self.verify_proof_validity(shard_id, vm_proof, transcript)?; - // println!("=> start_ec_sum: {:?}", shard_ec_sum); - // println!("=> shard_ec: {:?}", shard_ec); - // shard_ec_sum = shard_ec_sum + self.verify_proof_validity(shard_id, vm_proof, transcript)?; shard_ec_sum = shard_ec_sum + shard_ec; - // println!("=> new_ec_sum: {:?}", shard_ec_sum); Ok((Some(end_pc), shard_ec_sum)) })?; From 7f42d96a94f2cb0478e6179035d079f13f472f0d Mon Sep 17 00:00:00 2001 From: Ray Gao Date: Sun, 21 Dec 2025 18:56:08 -0500 Subject: [PATCH 09/14] ignore json [skip ci] --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b09cba9c7..61aac5754 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ proptest-regressions/ # ceno serialized files *.bin +*.json \ No newline at end of file From 6c789bbd34916a1643601ab1fc505b055c9b86af Mon Sep 17 00:00:00 2001 From: Ray Gao Date: Mon, 22 Dec 2025 17:49:58 -0500 Subject: [PATCH 10/14] verify root proof [skip ci] --- ceno_recursion/src/aggregation/mod.rs | 133 ++++++-------------------- 1 file changed, 29 insertions(+), 104 deletions(-) diff --git a/ceno_recursion/src/aggregation/mod.rs b/ceno_recursion/src/aggregation/mod.rs index f498ab0de..9d2bddaab 100644 --- a/ceno_recursion/src/aggregation/mod.rs +++ b/ceno_recursion/src/aggregation/mod.rs @@ -19,7 +19,10 @@ use openvm_circuit::{ system::program::trace::VmCommittedExe, utils::air_test_impl, }; -use openvm_stark_backend::config::{PcsProverData, Val}; +use openvm_stark_backend::{ + config::{PcsProverData, Val}, + verifier::VerificationError, +}; use internal::InternalVmVerifierConfig; use openvm_continuations::{ @@ -56,7 +59,7 @@ use openvm_stark_sdk::{ }; use p3::field::FieldAlgebra; use serde::{Deserialize, Serialize}; -use std::{sync::Arc, time::Instant}; +use std::{sync::Arc, time::Instant, fs::File}; pub type RecPcs = Basefold; use crate::aggregation::types::{InternalVmVerifierPvs, VmVerifierPvs}; use openvm_circuit::arch::{PUBLIC_VALUES_AIR_ID, SingleSegmentVmProver, instructions::exe::VmExe}; @@ -273,12 +276,14 @@ impl CenoAggregationProver { internal_vm_vk, internal_fri_params: internal_vm_pk.fri_params, internal_commit: internal_committed_exe.get_program_commit(), + root_vm_vk: root_vm_pk.vm_pk.get_vk(), }; let pk = CenoRecursionProvingKeys { leaf_vm_pk, leaf_committed_exe, internal_vm_pk, internal_committed_exe, + root_vm_pk, }; Self { @@ -409,12 +414,26 @@ impl CenoAggregationProver { public_values: user_public_values, }; - SingleSegmentVmProver::prove( + let root_proof = SingleSegmentVmProver::prove( &mut self.root_prover, root_input.write(), VM_MAX_TRACE_HEIGHTS, ) - .expect("root proof generation should pass") + .expect("root proof generation should pass"); + + // Export root proof + let file = File::create(format!("root_proof.bin")).expect("Create export proof file"); + bincode::serialize_into(file, &root_proof).expect("failed to serialize internal proof"); + + root_proof + } + + pub fn verify_root_proof( + &self, + root_proof: &Proof + ) -> Result<(), VerificationError> { + self.root_prover.vm.engine.verify(&self.vk.root_vm_vk, root_proof)?; + Ok(()) } } @@ -529,6 +548,7 @@ pub struct CenoRecursionVerifierKeys { pub internal_vm_vk: MultiStarkVerifyingKey, pub internal_fri_params: FriParameters, pub internal_commit: Com, + pub root_vm_vk: MultiStarkVerifyingKey, } #[derive(Serialize, Deserialize)] @@ -541,6 +561,7 @@ pub struct CenoRecursionProvingKeys { pub leaf_committed_exe: Arc>, pub internal_vm_pk: Arc>, pub internal_committed_exe: Arc>, + pub root_vm_pk: Arc>, } impl Clone for CenoRecursionProvingKeys { @@ -550,6 +571,7 @@ impl Clone for CenoRecursionProvingKeys { leaf_committed_exe: self.leaf_committed_exe.clone(), internal_vm_pk: self.internal_vm_pk.clone(), internal_committed_exe: self.internal_committed_exe.clone(), + root_vm_pk: self.root_vm_pk.clone(), } } } @@ -562,6 +584,7 @@ impl CenoRecursionProvingKeys { internal_vm_vk: self.internal_vm_pk.vm_pk.get_vk(), internal_fri_params: self.internal_vm_pk.fri_params, internal_commit: self.internal_committed_exe.get_program_commit(), + root_vm_vk: self.root_vm_pk.vm_pk.get_vk(), } } } @@ -611,104 +634,6 @@ pub(crate) fn chunk_ceno_leaf_proof_inputs( ret } -// Source from OpenVm SDK::verify_e2e_stark_proof with abridged key -// See: https://github.com/openvm-org/openvm -// pub fn verify_e2e_stark_proof( -// k: &CenoRecursionVerifierKeys, -// proof: &VmStarkProof, -// _expected_exe_commit: &Bn254Fr, -// _expected_vm_commit: &Bn254Fr, -// ) -> Result { -// if proof.inner.per_air.len() < 3 { -// return Err("Invalid number of AIRs: expected at least 3".into()); -// } else if proof.inner.per_air[0].air_id != PROGRAM_AIR_ID { -// return Err("Missing program AIR".into()); -// } else if proof.inner.per_air[1].air_id != CONNECTOR_AIR_ID { -// return Err("Missing connector AIR".into()); -// } else if proof.inner.per_air[2].air_id != PUBLIC_VALUES_AIR_ID { -// return Err("Missing public values AIR".into()); -// } -// let public_values_air_proof_data = &proof.inner.per_air[2]; -// -// let program_commit = proof.inner.commitments.main_trace[PROGRAM_CACHED_TRACE_INDEX].as_ref(); -// let internal_commit: &[_; CHUNK] = &k.internal_commit.into(); -// -// let (vm_vk, fri_params, vm_commit) = if program_commit == internal_commit { -// let internal_pvs: &InternalVmVerifierPvs<_> = public_values_air_proof_data -// .public_values -// .as_slice() -// .borrow(); -// if internal_commit != &internal_pvs.extra_pvs.internal_program_commit { -// return Err(format!( -// "Invalid internal program commit: expected {:?}, got {:?}", -// internal_commit, internal_pvs.extra_pvs.internal_program_commit -// )); -// } -// ( -// &k.internal_vm_vk, -// k.internal_fri_params, -// internal_pvs.extra_pvs.leaf_verifier_commit, -// ) -// } else { -// (&k.leaf_vm_vk, k.leaf_fri_params, *program_commit) -// }; -// let e = BabyBearPoseidon2Engine::new(fri_params); -// e.verify(vm_vk, &proof.inner) -// .expect("stark e2e proof verification should pass"); -// -// let pvs: &VmVerifierPvs<_> = -// public_values_air_proof_data.public_values[..VmVerifierPvs::::width()].borrow(); -// -// _debug: AIR ordering -// if let Some(exit_code) = pvs.connector.exit_code() { -// if exit_code != 0 { -// return Err(format!( -// "Invalid exit code: expected 0, got {}", -// exit_code -// )); -// } -// } else { -// return Err(format!("Program did not terminate")); -// } -// -// let hasher = vm_poseidon2_hasher(); -// let _public_values_root = hasher.merkle_root(&proof.user_public_values); -// _debug: Public value commitment -// if public_values_root != pvs.public_values_commit { -// return Err(format!( -// "Invalid public values root: expected {:?}, got {:?}", -// pvs.public_values_commit, -// public_values_root -// )); -// } -// -// let exe_commit = compute_exe_commit( -// &hasher, -// &pvs.app_commit, -// &pvs.memory.initial_root, -// pvs.connector.initial_pc, -// ); -// let app_commit = AppExecutionCommit::from_field_commit(exe_commit, vm_commit); -// let _exe_commit_bn254 = app_commit.app_exe_commit.to_bn254(); -// let _vm_commit_bn254 = app_commit.app_vm_commit.to_bn254(); -// -// _debug: execution commit checks -// if exe_commit_bn254 != *expected_exe_commit { -// return Err(eyre::eyre!( -// "Invalid app exe commit: expected {:?}, got {:?}", -// expected_exe_commit, -// exe_commit_bn254 -// )); -// } else if vm_commit_bn254 != *expected_vm_commit { -// return Err(eyre::eyre!( -// "Invalid app vm commit: expected {:?}, got {:?}", -// expected_vm_commit, -// vm_commit_bn254 -// )); -// } -// Ok(app_commit) -// } - /// Build Ceno's zkVM verifier program from vk in OpenVM's eDSL pub fn build_zkvm_verifier_program( vk: &ZKVMVerifyingKey>, @@ -843,7 +768,7 @@ mod tests { } #[test] - #[ignore = "need to generate proof first"] + // #[ignore = "need to generate proof first"] pub fn test_aggregation() { let stack_size = 256 * 1024 * 1024; // 64 MB @@ -856,7 +781,7 @@ mod tests { } #[test] - #[ignore = "need to generate proof first"] + // #[ignore = "need to generate proof first"] pub fn test_single() { let stack_size = 256 * 1024 * 1024; // 64 MB From 837b7b23fb6ae5d44f8ee59a2ca8de7086783389 Mon Sep 17 00:00:00 2001 From: Ray Gao Date: Mon, 22 Dec 2025 18:25:56 -0500 Subject: [PATCH 11/14] verify root proof --- ceno_recursion/src/aggregation/mod.rs | 34 +++++++++++---------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/ceno_recursion/src/aggregation/mod.rs b/ceno_recursion/src/aggregation/mod.rs index 9d2bddaab..78cdf70f5 100644 --- a/ceno_recursion/src/aggregation/mod.rs +++ b/ceno_recursion/src/aggregation/mod.rs @@ -59,7 +59,7 @@ use openvm_stark_sdk::{ }; use p3::field::FieldAlgebra; use serde::{Deserialize, Serialize}; -use std::{sync::Arc, time::Instant, fs::File}; +use std::{fs::File, sync::Arc, time::Instant}; pub type RecPcs = Basefold; use crate::aggregation::types::{InternalVmVerifierPvs, VmVerifierPvs}; use openvm_circuit::arch::{PUBLIC_VALUES_AIR_ID, SingleSegmentVmProver, instructions::exe::VmExe}; @@ -422,18 +422,18 @@ impl CenoAggregationProver { .expect("root proof generation should pass"); // Export root proof - let file = File::create(format!("root_proof.bin")).expect("Create export proof file"); + let file = File::create("root_proof.bin").expect("Create export proof file"); bincode::serialize_into(file, &root_proof).expect("failed to serialize internal proof"); root_proof } - pub fn verify_root_proof( - &self, - root_proof: &Proof - ) -> Result<(), VerificationError> { - self.root_prover.vm.engine.verify(&self.vk.root_vm_vk, root_proof)?; - Ok(()) + pub fn verify_root_proof(&self, root_proof: &Proof) -> Result<(), VerificationError> { + self.root_prover + .vm + .engine + .verify(&self.vk.root_vm_vk, root_proof)?; + Ok(()) } } @@ -720,16 +720,10 @@ mod tests { .expect("Failed to deserialize vk file"); let mut agg_prover = CenoAggregationProver::from_base_vk(vk); - let _root_proof = agg_prover.generate_root_proof(zkvm_proofs); - - // _todo: add a verification step after root proof generation - // verify_e2e_stark_proof( - // &agg_prover.vk, - // &root_stark_proof, - // &Bn254Fr::ZERO, - // &Bn254Fr::ZERO, - // ) - // .expect("Verify e2e stark proof should pass"); + let root_proof = agg_prover.generate_root_proof(zkvm_proofs); + agg_prover + .verify_root_proof(&root_proof) + .expect("root proof verification should pass"); } pub fn verify_single_inner_thread() { @@ -768,7 +762,7 @@ mod tests { } #[test] - // #[ignore = "need to generate proof first"] + #[ignore = "need to generate proof first"] pub fn test_aggregation() { let stack_size = 256 * 1024 * 1024; // 64 MB @@ -781,7 +775,7 @@ mod tests { } #[test] - // #[ignore = "need to generate proof first"] + #[ignore = "need to generate proof first"] pub fn test_single() { let stack_size = 256 * 1024 * 1024; // 64 MB From 1e6c6df0af6ca5798389fd1a4c8fbadd6b8c888c Mon Sep 17 00:00:00 2001 From: Ray Gao Date: Tue, 23 Dec 2025 04:45:48 -0500 Subject: [PATCH 12/14] verify root proof and restore sdk --- ceno_cli/src/sdk.rs | 36 ++++++++++++++++++++++----- ceno_recursion/src/aggregation/mod.rs | 24 +++++++++++++++++- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/ceno_cli/src/sdk.rs b/ceno_cli/src/sdk.rs index 39d9d7a39..b5f980581 100644 --- a/ceno_cli/src/sdk.rs +++ b/ceno_cli/src/sdk.rs @@ -21,10 +21,15 @@ use gkr_iop::hal::ProverBackend; use mpcs::{Basefold, BasefoldRSParams, PolynomialCommitmentScheme, SecurityLevel}; #[cfg(feature = "gpu")] use openvm_cuda_backend::engine::GpuBabyBearPoseidon2Engine as BabyBearPoseidon2Engine; -use openvm_native_circuit::NativeConfig; -use openvm_sdk::RootSC; +use openvm_native_circuit::{NativeBuilder, NativeConfig}; +use openvm_sdk::{RootSC, prover::vm::new_local_prover}; use openvm_stark_backend::{config::StarkGenericConfig, proof::Proof}; -use openvm_stark_sdk::config::baby_bear_poseidon2::BabyBearPoseidon2Config; +#[cfg(not(feature = "gpu"))] +use openvm_stark_sdk::config::baby_bear_poseidon2::BabyBearPoseidon2Engine; +use openvm_stark_sdk::config::{ + baby_bear_poseidon2::BabyBearPoseidon2Config, + baby_bear_poseidon2_root::BabyBearPoseidon2RootEngine, +}; use serde::Serialize; use std::sync::Arc; @@ -192,11 +197,30 @@ where &mut self, base_proofs: Vec>>, ) -> Proof { + let vb = NativeBuilder::default(); + // TODO: cache agg_prover - let mut agg_prover = if let Some(_agg_pk) = self.agg_pk.as_ref() { - CenoAggregationProver::from_base_vk( - self.zkvm_vk.clone().expect("zkvm_vk need to be set"), + let mut agg_prover = if let Some(agg_pk) = self.agg_pk.as_ref() { + let leaf_prover = new_local_prover::( + vb.clone(), + &agg_pk.leaf_vm_pk, + agg_pk.leaf_committed_exe.exe.clone(), ) + .expect("leaf prover"); + let internal_prover = new_local_prover::( + vb.clone(), + &agg_pk.internal_vm_pk, + agg_pk.internal_committed_exe.exe.clone(), + ) + .expect("internal prover"); + let root_prover = new_local_prover::( + vb.clone(), + &agg_pk.root_vm_pk, + agg_pk.root_committed_exe.exe.clone(), + ) + .expect("root prover"); + + CenoAggregationProver::new(leaf_prover, internal_prover, root_prover, agg_pk.clone()) } else { let agg_prover = CenoAggregationProver::from_base_vk(self.zkvm_vk.clone().unwrap()); self.agg_pk = Some(agg_prover.pk.clone()); diff --git a/ceno_recursion/src/aggregation/mod.rs b/ceno_recursion/src/aggregation/mod.rs index 78cdf70f5..8552b39e3 100644 --- a/ceno_recursion/src/aggregation/mod.rs +++ b/ceno_recursion/src/aggregation/mod.rs @@ -87,6 +87,7 @@ const VM_MAX_TRACE_HEIGHTS: &[u32] = &[ 4194304, 4, 128, 2097152, 8388608, 4194304, 262144, 8388608, 16777216, 2097152, 16777216, 2097152, 8388608, 262144, 2097152, 1048576, 4194304, 1048576, 262144, ]; + pub struct CenoAggregationProver { pub leaf_prover: VmInstance, pub internal_prover: VmInstance, @@ -284,6 +285,7 @@ impl CenoAggregationProver { internal_vm_pk, internal_committed_exe, root_vm_pk, + root_committed_exe, }; Self { @@ -437,6 +439,17 @@ impl CenoAggregationProver { } } +pub fn verify_root_proof( + vk: &MultiStarkVerifyingKey, + root_proof: &Proof, +) -> Result<(), VerificationError> { + let root_fri_params = + FriParameters::standard_with_100_bits_conjectured_security(ROOT_LOG_BLOWUP); + let root_engine = BabyBearPoseidon2RootEngine::new(root_fri_params); + root_engine.verify(vk, root_proof)?; + Ok(()) +} + /// Config to generate leaf VM verifier program. pub struct CenoLeafVmVerifierConfig { pub vk: ZKVMVerifyingKey>, @@ -562,6 +575,7 @@ pub struct CenoRecursionProvingKeys { pub internal_vm_pk: Arc>, pub internal_committed_exe: Arc>, pub root_vm_pk: Arc>, + pub root_committed_exe: Arc>, } impl Clone for CenoRecursionProvingKeys { @@ -572,6 +586,7 @@ impl Clone for CenoRecursionProvingKeys { internal_vm_pk: self.internal_vm_pk.clone(), internal_committed_exe: self.internal_committed_exe.clone(), root_vm_pk: self.root_vm_pk.clone(), + root_committed_exe: self.root_committed_exe.clone(), } } } @@ -693,7 +708,7 @@ pub fn verify_proofs( #[cfg(test)] mod tests { use crate::{ - aggregation::{CenoAggregationProver, verify_proofs}, + aggregation::{CenoAggregationProver, verify_proofs, verify_root_proof}, zkvm_verifier::binding::E, }; use ceno_zkvm::{ @@ -721,9 +736,16 @@ mod tests { let mut agg_prover = CenoAggregationProver::from_base_vk(vk); let root_proof = agg_prover.generate_root_proof(zkvm_proofs); + + // Verify generated aggregated root_proof + // Method 1: Verify the root proof using the aggregation prover agg_prover .verify_root_proof(&root_proof) .expect("root proof verification should pass"); + + // Method 2: Use stand-alone verification with only vk + let vk = agg_prover.vk.root_vm_vk; + verify_root_proof(&vk, &root_proof).expect("root proof verification should pass"); } pub fn verify_single_inner_thread() { From bb3eb54b2eb7afbe76d1eea24888f20115179bb5 Mon Sep 17 00:00:00 2001 From: "sm.wu" Date: Wed, 24 Dec 2025 11:14:27 +0800 Subject: [PATCH 13/14] misc refactor --- ceno_cli/src/sdk.rs | 6 ++--- ceno_recursion/src/aggregation/mod.rs | 34 +++++++++++++++++---------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/ceno_cli/src/sdk.rs b/ceno_cli/src/sdk.rs index b5f980581..3f5b63e28 100644 --- a/ceno_cli/src/sdk.rs +++ b/ceno_cli/src/sdk.rs @@ -21,7 +21,7 @@ use gkr_iop::hal::ProverBackend; use mpcs::{Basefold, BasefoldRSParams, PolynomialCommitmentScheme, SecurityLevel}; #[cfg(feature = "gpu")] use openvm_cuda_backend::engine::GpuBabyBearPoseidon2Engine as BabyBearPoseidon2Engine; -use openvm_native_circuit::{NativeBuilder, NativeConfig}; +use openvm_native_circuit::{NativeBuilder, NativeConfig, NativeCpuBuilder}; use openvm_sdk::{RootSC, prover::vm::new_local_prover}; use openvm_stark_backend::{config::StarkGenericConfig, proof::Proof}; #[cfg(not(feature = "gpu"))] @@ -213,8 +213,8 @@ where agg_pk.internal_committed_exe.exe.clone(), ) .expect("internal prover"); - let root_prover = new_local_prover::( - vb.clone(), + let root_prover = new_local_prover::( + Default::default(), &agg_pk.root_vm_pk, agg_pk.root_committed_exe.exe.clone(), ) diff --git a/ceno_recursion/src/aggregation/mod.rs b/ceno_recursion/src/aggregation/mod.rs index 8552b39e3..ed0db2524 100644 --- a/ceno_recursion/src/aggregation/mod.rs +++ b/ceno_recursion/src/aggregation/mod.rs @@ -31,7 +31,7 @@ use openvm_continuations::{ }; #[cfg(feature = "gpu")] use openvm_cuda_backend::engine::GpuBabyBearPoseidon2Engine as BabyBearPoseidon2Engine; -use openvm_native_circuit::{NativeBuilder, NativeConfig}; +use openvm_native_circuit::{NativeBuilder, NativeConfig, NativeCpuBuilder}; use openvm_native_compiler::{ asm::AsmBuilder, conversion::{CompilerOptions, convert_program}, @@ -52,6 +52,7 @@ use openvm_stark_sdk::config::baby_bear_poseidon2::BabyBearPoseidon2Engine; use openvm_stark_sdk::{ config::{ FriParameters, baby_bear_poseidon2::BabyBearPoseidon2Config, + baby_bear_poseidon2_root::BabyBearPoseidon2RootEngine, fri_params::standard_fri_params_with_100_bits_conjectured_security, }, engine::StarkFriEngine, @@ -70,7 +71,6 @@ use openvm_native_compiler::{ }; use openvm_sdk::util::check_max_constraint_degrees; use openvm_stark_backend::proof::Proof; -use openvm_stark_sdk::config::baby_bear_poseidon2_root::BabyBearPoseidon2RootEngine; mod internal; mod root; @@ -91,7 +91,7 @@ const VM_MAX_TRACE_HEIGHTS: &[u32] = &[ pub struct CenoAggregationProver { pub leaf_prover: VmInstance, pub internal_prover: VmInstance, - pub root_prover: VmInstance, + pub root_prover: VmInstance, pub vk: CenoRecursionVerifierKeys, pub pk: CenoRecursionProvingKeys, } @@ -100,7 +100,7 @@ impl CenoAggregationProver { pub fn new( leaf_prover: VmInstance, internal_prover: VmInstance, - root_prover: VmInstance, + root_prover: VmInstance, pk: CenoRecursionProvingKeys, ) -> Self { Self { @@ -242,9 +242,12 @@ impl CenoAggregationProver { let mut root_engine = BabyBearPoseidon2RootEngine::new(root_fri_params); root_engine.max_constraint_degree = ROOT_MAX_CONSTRAINT_DEG; - let (root_vm, root_vm_pk) = - VirtualMachine::new_with_keygen(root_engine, vb.clone(), root_vm_config.clone()) - .expect("root keygen"); + let (root_vm, root_vm_pk) = VirtualMachine::<_, NativeCpuBuilder>::new_with_keygen( + root_engine, + Default::default(), + root_vm_config.clone(), + ) + .expect("root keygen"); let root_program = CenoRootVmVerifierConfig { leaf_fri_params, internal_fri_params, @@ -263,8 +266,8 @@ impl CenoAggregationProver { vm_config: root_vm_config, vm_pk: root_vm_pk, }); - let root_prover = new_local_prover::( - vb.clone(), + let root_prover = new_local_prover::( + Default::default(), &root_vm_pk, root_committed_exe.exe.clone(), ) @@ -416,12 +419,17 @@ impl CenoAggregationProver { public_values: user_public_values, }; + let root_start_timestamp = Instant::now(); let root_proof = SingleSegmentVmProver::prove( &mut self.root_prover, root_input.write(), VM_MAX_TRACE_HEIGHTS, ) .expect("root proof generation should pass"); + println!( + "Root - Completed root proof at: {:?}", + root_start_timestamp.elapsed() + ); // Export root proof let file = File::create("root_proof.bin").expect("Create export proof file"); @@ -440,13 +448,13 @@ impl CenoAggregationProver { } pub fn verify_root_proof( - vk: &MultiStarkVerifyingKey, + vk: &CenoRecursionVerifierKeys, root_proof: &Proof, ) -> Result<(), VerificationError> { let root_fri_params = FriParameters::standard_with_100_bits_conjectured_security(ROOT_LOG_BLOWUP); let root_engine = BabyBearPoseidon2RootEngine::new(root_fri_params); - root_engine.verify(vk, root_proof)?; + root_engine.verify(&vk.root_vm_vk, root_proof)?; Ok(()) } @@ -744,8 +752,8 @@ mod tests { .expect("root proof verification should pass"); // Method 2: Use stand-alone verification with only vk - let vk = agg_prover.vk.root_vm_vk; - verify_root_proof(&vk, &root_proof).expect("root proof verification should pass"); + verify_root_proof(&agg_prover.vk, &root_proof) + .expect("root proof verification should pass"); } pub fn verify_single_inner_thread() { From 7c76947fdfdfdc5e999167ab6edfabd56796693e Mon Sep 17 00:00:00 2001 From: Ray Gao Date: Thu, 15 Jan 2026 19:01:11 -0500 Subject: [PATCH 14/14] fmt --- ceno_cli/src/sdk.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/ceno_cli/src/sdk.rs b/ceno_cli/src/sdk.rs index ebceee0d9..29880230b 100644 --- a/ceno_cli/src/sdk.rs +++ b/ceno_cli/src/sdk.rs @@ -217,14 +217,21 @@ where self.zkvm_vk.is_some(), "Aggregation must provide existing base layer vk." ); - let base_vk = self.zkvm_vk.as_ref().unwrap().clone(); let root_prover = new_local_prover::( + let base_vk = self.zkvm_vk.as_ref().unwrap().clone(); + let root_prover = new_local_prover::( Default::default(), &agg_pk.root_vm_pk, agg_pk.root_committed_exe.exe.clone(), ) .expect("root prover"); - CenoAggregationProver::new(base_vk, leaf_prover, internal_prover, root_prover, agg_pk.clone()) + CenoAggregationProver::new( + base_vk, + leaf_prover, + internal_prover, + root_prover, + agg_pk.clone(), + ) } else { let agg_prover = CenoAggregationProver::from_base_vk(self.zkvm_vk.clone().unwrap()); self.agg_pk = Some(agg_prover.pk.clone());