diff --git a/jolt-core/src/poly/commitment/dory/dory_globals.rs b/jolt-core/src/poly/commitment/dory/dory_globals.rs index 8772532d78..90c2bb0628 100644 --- a/jolt-core/src/poly/commitment/dory/dory_globals.rs +++ b/jolt-core/src/poly/commitment/dory/dory_globals.rs @@ -5,13 +5,10 @@ use allocative::Allocative; use dory::backends::arkworks::{init_cache, ArkG1, ArkG2}; use std::sync::{ atomic::{AtomicU8, Ordering}, - RwLock, + Mutex, MutexGuard, OnceLock, RwLock, }; #[cfg(test)] -use std::{ - sync::OnceLock, - time::{SystemTime, UNIX_EPOCH}, -}; +use std::time::{SystemTime, UNIX_EPOCH}; /// Dory matrix layout for OneHot polynomials. /// @@ -185,6 +182,12 @@ pub struct DoryContextGuard { previous_context: DoryContext, } +pub struct DoryRuntimeGuard { + _guard: MutexGuard<'static, ()>, +} + +static DORY_RUNTIME_GUARD: OnceLock> = OnceLock::new(); + impl Drop for DoryContextGuard { fn drop(&mut self) { CURRENT_CONTEXT.store(self.previous_context as u8, Ordering::SeqCst); @@ -195,6 +198,13 @@ impl Drop for DoryContextGuard { pub struct DoryGlobals; impl DoryGlobals { + pub fn acquire_runtime_guard() -> DoryRuntimeGuard { + let mutex = DORY_RUNTIME_GUARD.get_or_init(|| Mutex::new(())); + DoryRuntimeGuard { + _guard: mutex.lock().unwrap(), + } + } + #[cfg(test)] pub(crate) fn configure_test_cache_root() { static TEST_CACHE_ROOT: OnceLock<()> = OnceLock::new(); diff --git a/jolt-core/src/poly/opening_proof.rs b/jolt-core/src/poly/opening_proof.rs index dc2bfbd1b4..411efb6e47 100644 --- a/jolt-core/src/poly/opening_proof.rs +++ b/jolt-core/src/poly/opening_proof.rs @@ -11,7 +11,6 @@ use crate::{ }; use allocative::Allocative; use num_derive::FromPrimitive; -#[cfg(test)] use std::cell::RefCell; use std::collections::{BTreeMap, HashMap}; use std::sync::Arc; @@ -22,6 +21,7 @@ use super::{ use crate::{ field::JoltField, transcripts::Transcript, + utils::errors::ProofVerifyError, zkvm::witness::{CommittedPolynomial, VirtualPolynomial}, }; @@ -212,9 +212,7 @@ where opening_ids_by_poly: BTreeMap>, /// Maps an `OpeningId` that was deduplicated to its canonical representative. pub aliases: BTreeMap, - #[cfg(test)] pub appended_virtual_openings: RefCell>, - #[cfg(test)] pub appended_committed_openings: RefCell>, pub log_T: usize, #[allocative(skip)] @@ -245,6 +243,8 @@ where pub zk_mode: bool, pending_claims: Vec, pending_claim_ids: Vec, + missing_opening: RefCell>, + malformed_proof: RefCell>, } pub trait OpeningAccumulator { @@ -343,7 +343,6 @@ impl OpeningAccumulator for ProverOpeningAccumulator { .openings .get(&key) .unwrap_or_else(|| panic!("opening for {sumcheck:?} {polynomial:?} not found")); - #[cfg(test)] { let mut virtual_openings = self.appended_virtual_openings.borrow_mut(); if let Some(index) = virtual_openings.iter().position(|id| id == &key) { @@ -364,7 +363,6 @@ impl OpeningAccumulator for ProverOpeningAccumulator { .openings .get(&key) .unwrap_or_else(|| panic!("opening for {sumcheck:?} {polynomial:?} not found")); - #[cfg(test)] { let mut committed_openings = self.appended_committed_openings.borrow_mut(); if let Some(index) = committed_openings.iter().position(|id| id == &key) { @@ -385,7 +383,6 @@ impl OpeningAccumulator for ProverOpeningAccumulator { }; let key = self.resolve_alias(opening_id); let (point, claim) = self.openings.get(&key)?; - #[cfg(test)] { let mut committed_openings = self.appended_committed_openings.borrow_mut(); if let Some(index) = committed_openings.iter().position(|id| id == &key) { @@ -405,9 +402,7 @@ where openings: BTreeMap::new(), opening_ids_by_poly: BTreeMap::new(), aliases: BTreeMap::new(), - #[cfg(test)] appended_virtual_openings: std::cell::RefCell::new(vec![]), - #[cfg(test)] appended_committed_openings: std::cell::RefCell::new(vec![]), log_T, pending_claims: Vec::new(), @@ -465,7 +460,6 @@ where point: OpeningPoint, claim: F, ) -> bool { - #[cfg(test)] let should_track_committed = matches!(underlying_polynomial_id(key), PolynomialId::Committed(_)) && !point.r.is_empty(); @@ -488,7 +482,6 @@ where self.pending_claim_ids.push(key); self.openings.insert(key, (point, claim)); self.index_opening_id(key); - #[cfg(test)] if should_track_committed { self.appended_committed_openings.borrow_mut().push(key); } @@ -538,7 +531,6 @@ where ) { let key = OpeningId::virt(polynomial, sumcheck); if self.insert_or_alias_opening(key, opening_point, claim) { - #[cfg(test)] self.appended_virtual_openings.borrow_mut().push(key); } } @@ -577,6 +569,15 @@ where pub fn take_pending_claim_ids(&mut self) -> Vec { std::mem::take(&mut self.pending_claim_ids) } + + pub fn assert_all_openings_consumed(&self) { + let missing_virtual = self.appended_virtual_openings.borrow(); + let missing_committed = self.appended_committed_openings.borrow(); + assert!( + missing_virtual.is_empty() && missing_committed.is_empty(), + "Not all openings have been proven. Missing virtual: {missing_virtual:?}. Missing committed: {missing_committed:?}", + ); + } } impl Default for VerifierOpeningAccumulator @@ -596,11 +597,10 @@ impl OpeningAccumulator for VerifierOpeningAccumulator { ) -> (OpeningPoint, F) { let requested = OpeningId::Polynomial(PolynomialId::Virtual(polynomial), sumcheck); let key = self.resolve_alias(requested); - let (point, claim) = self - .openings + self.openings .get(&key) - .unwrap_or_else(|| panic!("No opening found for {sumcheck:?} {polynomial:?}")); - (point.clone(), *claim) + .map(|(point, claim)| (point.clone(), *claim)) + .unwrap_or_else(|| self.record_missing_opening(key)) } fn get_committed_polynomial_opening( @@ -610,11 +610,10 @@ impl OpeningAccumulator for VerifierOpeningAccumulator { ) -> (OpeningPoint, F) { let requested = OpeningId::Polynomial(PolynomialId::Committed(polynomial), sumcheck); let key = self.resolve_alias(requested); - let (point, claim) = self - .openings + self.openings .get(&key) - .unwrap_or_else(|| panic!("No opening found for {sumcheck:?} {polynomial:?}")); - (point.clone(), *claim) + .map(|(point, claim)| (point.clone(), *claim)) + .unwrap_or_else(|| self.record_missing_opening(key)) } fn get_advice_opening( @@ -647,7 +646,31 @@ where zk_mode, pending_claims: Vec::new(), pending_claim_ids: Vec::new(), + missing_opening: RefCell::new(None), + malformed_proof: RefCell::new(None), + } + } + + fn record_missing_opening(&self, key: OpeningId) -> (OpeningPoint, F) { + let mut missing = self.missing_opening.borrow_mut(); + if missing.is_none() { + *missing = Some(key); + } + let dummy_len = self.log_T.max(1) + 128; + ( + OpeningPoint::new(vec![F::Challenge::default(); dummy_len]), + F::zero(), + ) + } + + pub fn take_missing_opening_error(&self) -> Result<(), ProofVerifyError> { + if let Some(message) = self.malformed_proof.borrow_mut().take() { + return Err(ProofVerifyError::MalformedProof(message)); + } + if let Some(opening_id) = self.missing_opening.borrow_mut().take() { + return Err(ProofVerifyError::MissingOpening(format!("{opening_id:?}"))); } + Ok(()) } fn resolve_alias(&self, mut key: OpeningId) -> OpeningId { @@ -702,10 +725,15 @@ where self.find_existing_opening_at_point(underlying_polynomial_id(key), &point) { if existing_id != key { - assert_eq!( - *claim, existing_claim, - "Inconsistent duplicate opening claims: {key:?} vs {existing_id:?}" - ); + if *claim != existing_claim { + let mut malformed = self.malformed_proof.borrow_mut(); + if malformed.is_none() { + *malformed = Some(format!( + "inconsistent duplicate opening claims: {key:?} vs {existing_id:?}" + )); + } + return; + } self.aliases.insert(key, existing_id); return; } @@ -847,3 +875,24 @@ pub fn compute_advice_lagrange_factor( }) .product() } + +#[cfg(test)] +mod tests { + use super::*; + use ark_bn254::Fr; + + #[test] + fn verifier_accumulator_reports_missing_opening() { + let accumulator = VerifierOpeningAccumulator::::new(4, false); + let _ = accumulator.get_committed_polynomial_opening( + CommittedPolynomial::InstructionRa(0), + SumcheckId::HammingWeightClaimReduction, + ); + + assert!(matches!( + accumulator.take_missing_opening_error(), + Err(ProofVerifyError::MissingOpening(message)) + if message.contains("InstructionRa(0)") + )); + } +} diff --git a/jolt-core/src/utils/errors.rs b/jolt-core/src/utils/errors.rs index 2ca3d05e99..613e4d5f1a 100644 --- a/jolt-core/src/utils/errors.rs +++ b/jolt-core/src/utils/errors.rs @@ -24,12 +24,22 @@ pub enum ProofVerifyError { InvalidKeyLength(usize), #[error("Invalid opening proof -- the proof failed to verify")] InvalidOpeningProof, + #[error("Invalid trace_length: {0}")] + InvalidTraceLength(usize), + #[error("trace_length {0} exceeds preprocessing maximum {1}")] + TraceLengthTooLarge(usize, usize), #[error("Invalid read-write checking configuration: {0}")] InvalidReadWriteConfig(String), #[error("Invalid one-hot configuration: {0}")] InvalidOneHotConfig(String), #[error("Invalid ram_K: got {0}, minimum required {1}")] InvalidRamK(usize, usize), + #[error("Invalid ram_K: got {0}, maximum supported {1}")] + RamKTooLarge(usize, usize), + #[error("Malformed proof: {0}")] + MalformedProof(String), + #[error("Missing opening: {0}")] + MissingOpening(String), #[error("Dory proof verification failed: {0}")] DoryError(String), #[error("Sumcheck verification failed")] diff --git a/jolt-core/src/zkvm/claim_reductions/ram_ra.rs b/jolt-core/src/zkvm/claim_reductions/ram_ra.rs index 9dbbf880b3..74e7bf6109 100644 --- a/jolt-core/src/zkvm/claim_reductions/ram_ra.rs +++ b/jolt-core/src/zkvm/claim_reductions/ram_ra.rs @@ -629,8 +629,8 @@ impl RaReductionParams { let (r_address_val, r_cycle_val) = r_val.split_at_r(log_K); // Verify unified address (these should hold by construction after Stage 2 alignment). - debug_assert_eq!(r_address_raf, r_address_rw); - debug_assert_eq!(r_address_raf, r_address_val); + assert_eq!(r_address_raf, r_address_rw); + assert_eq!(r_address_raf, r_address_val); // Sample γ for combining claims let gamma: F = transcript.challenge_scalar(); diff --git a/jolt-core/src/zkvm/mod.rs b/jolt-core/src/zkvm/mod.rs index 92d23a1ee3..820bb49310 100644 --- a/jolt-core/src/zkvm/mod.rs +++ b/jolt-core/src/zkvm/mod.rs @@ -1,6 +1,8 @@ use std::fs::File; +use crate::poly::commitment::dory::DoryLayout; use crate::zkvm::config::OneHotParams; +use crate::zkvm::config::{OneHotConfig, ReadWriteConfig}; use crate::zkvm::witness::CommittedPolynomial; use crate::{ curve::Bn254Curve, @@ -166,11 +168,15 @@ where } /// Absorb public instance data into the transcript for Fiat-Shamir. +#[allow(clippy::too_many_arguments)] pub fn fiat_shamir_preamble( program_io: &JoltDevice, ram_K: usize, trace_length: usize, entry_address: u64, + rw_config: &ReadWriteConfig, + one_hot_config: &OneHotConfig, + dory_layout: DoryLayout, transcript: &mut impl Transcript, ) { transcript.append_u64(b"max_input_size", program_io.memory_layout.max_input_size); @@ -182,6 +188,28 @@ pub fn fiat_shamir_preamble( transcript.append_u64(b"ram_K", ram_K as u64); transcript.append_u64(b"trace_length", trace_length as u64); transcript.append_u64(b"entry_address", entry_address); + transcript.append_u64( + b"ram_rw_phase1_num_rounds", + rw_config.ram_rw_phase1_num_rounds as u64, + ); + transcript.append_u64( + b"ram_rw_phase2_num_rounds", + rw_config.ram_rw_phase2_num_rounds as u64, + ); + transcript.append_u64( + b"registers_rw_phase1_num_rounds", + rw_config.registers_rw_phase1_num_rounds as u64, + ); + transcript.append_u64( + b"registers_rw_phase2_num_rounds", + rw_config.registers_rw_phase2_num_rounds as u64, + ); + transcript.append_u64(b"log_k_chunk", one_hot_config.log_k_chunk as u64); + transcript.append_u64( + b"lookups_ra_virtual_log_k_chunk", + one_hot_config.lookups_ra_virtual_log_k_chunk as u64, + ); + transcript.append_u64(b"dory_layout", u8::from(dory_layout) as u64); } #[cfg(feature = "prover")] diff --git a/jolt-core/src/zkvm/prover.rs b/jolt-core/src/zkvm/prover.rs index 8d58def459..2c91e2bdfe 100644 --- a/jolt-core/src/zkvm/prover.rs +++ b/jolt-core/src/zkvm/prover.rs @@ -487,6 +487,9 @@ impl< Option>, ) { let _pprof_prove = pprof_scope!("prove"); + let _dory_runtime_guard = DoryGlobals::acquire_runtime_guard(); + let dory_layout = DoryGlobals::get_layout(); + let one_hot_config = self.one_hot_params.to_config(); #[cfg(not(target_arch = "wasm32"))] let start = Instant::now(); @@ -495,6 +498,9 @@ impl< self.one_hot_params.ram_k, self.trace.len(), self.preprocessing.shared.bytecode.entry_address, + &self.rw_config, + &one_hot_config, + dory_layout, &mut self.transcript, ); @@ -537,18 +543,7 @@ impl< let opening_claims = crate::zkvm::proof_serialization::Claims(self.opening_accumulator.openings.clone()); - #[cfg(test)] - { - let missing_virtual = self.opening_accumulator.appended_virtual_openings.borrow(); - let missing_committed = self - .opening_accumulator - .appended_committed_openings - .borrow(); - assert!( - missing_virtual.is_empty() && missing_committed.is_empty(), - "Not all openings have been proven. Missing virtual: {missing_virtual:?}. Missing committed: {missing_committed:?}", - ); - } + self.opening_accumulator.assert_all_openings_consumed(); #[cfg(test)] let debug_info = Some(ProverDebugInfo { @@ -579,8 +574,8 @@ impl< trace_length: self.trace.len(), ram_K: self.one_hot_params.ram_k, rw_config: self.rw_config.clone(), - one_hot_config: self.one_hot_params.to_config(), - dory_layout: DoryGlobals::get_layout(), + one_hot_config, + dory_layout, }; #[cfg(not(target_arch = "wasm32"))] @@ -706,7 +701,7 @@ impl< } else { // CycleMajor: use streaming let row_len = DoryGlobals::get_num_columns(); - let num_rows = T / DoryGlobals::get_max_num_rows(); + let num_rows = T / row_len; tracing::debug!( "Generating and committing {} witness polynomials with T={}, row_len={}, num_rows={}", @@ -2242,14 +2237,15 @@ mod tests { multilinear_polynomial::MultilinearPolynomial, opening_proof::{OpeningAccumulator, SumcheckId}, }; + use crate::utils::errors::ProofVerifyError; use crate::zkvm::claim_reductions::AdviceKind; use crate::zkvm::verifier::JoltSharedPreprocessing; use crate::zkvm::witness::CommittedPolynomial; use crate::zkvm::{ prover::JoltProverPreprocessing, - ram::populate_memory_states, + ram::{compute_max_ram_K, compute_min_ram_K, populate_memory_states}, verifier::{JoltVerifier, JoltVerifierPreprocessing}, - RV64IMACProver, RV64IMACVerifier, + RV64IMACProof, RV64IMACProver, RV64IMACVerifier, }; #[cfg(feature = "zk")] use crate::{curve::JoltCurve, field::JoltField}; @@ -2275,6 +2271,42 @@ mod tests { (commitments, coeffs, blindings) } + fn prove_fibonacci( + input: u8, + ) -> ( + JoltVerifierPreprocessing, + RV64IMACProof, + tracer::JoltDevice, + ) { + DoryGlobals::reset(); + let mut program = host::Program::new("fibonacci-guest"); + let inputs = postcard::to_stdvec(&input).unwrap(); + let (bytecode, init_memory_state, _, e_entry) = program.decode(); + let (lazy_trace, trace, final_memory_state, program_io) = program.trace(&inputs, &[], &[]); + + let shared = JoltSharedPreprocessing::new( + bytecode, + program_io.memory_layout.clone(), + init_memory_state, + 1 << 16, + e_entry, + ) + .unwrap(); + let prover_preprocessing = JoltProverPreprocessing::new(shared); + let prover = RV64IMACProver::gen_from_trace( + &prover_preprocessing, + lazy_trace, + trace, + program_io.clone(), + None, + None, + final_memory_state, + ); + let (proof, _) = prover.prove(); + let verifier_preprocessing = JoltVerifierPreprocessing::from(&prover_preprocessing); + (verifier_preprocessing, proof, program_io) + } + fn commit_trusted_advice_preprocessing_only( preprocessing: &JoltProverPreprocessing, trusted_advice_bytes: &[u8], @@ -3313,6 +3345,86 @@ mod tests { verifier.verify().unwrap(); } + #[test] + #[serial] + fn verifier_rejects_invalid_trace_lengths() { + let (verifier_preprocessing, mut zero_trace, program_io) = prove_fibonacci(9); + zero_trace.trace_length = 0; + assert!(matches!( + RV64IMACVerifier::new(&verifier_preprocessing, zero_trace, program_io, None, None), + Err(ProofVerifyError::InvalidTraceLength(0)) + )); + + let (verifier_preprocessing, mut non_power_of_two, program_io) = prove_fibonacci(9); + non_power_of_two.trace_length = 3; + assert!(matches!( + RV64IMACVerifier::new( + &verifier_preprocessing, + non_power_of_two, + program_io, + None, + None + ), + Err(ProofVerifyError::InvalidTraceLength(3)) + )); + + let (verifier_preprocessing, mut oversized, program_io) = prove_fibonacci(9); + oversized.trace_length = verifier_preprocessing.shared.max_padded_trace_length * 2; + assert!(matches!( + RV64IMACVerifier::new(&verifier_preprocessing, oversized, program_io, None, None), + Err(ProofVerifyError::TraceLengthTooLarge(_, _)) + )); + } + + #[test] + #[serial] + fn verifier_rejects_invalid_ram_k_bounds() { + let (verifier_preprocessing, mut undersized, program_io) = prove_fibonacci(9); + let min_ram_k = compute_min_ram_K( + &verifier_preprocessing.shared.ram, + &verifier_preprocessing.shared.memory_layout, + ); + let max_ram_k = compute_max_ram_K(&verifier_preprocessing.shared.memory_layout); + + undersized.ram_K = min_ram_k.checked_shr(1).unwrap_or(0); + assert!(matches!( + RV64IMACVerifier::new(&verifier_preprocessing, undersized, program_io, None, None), + Err(ProofVerifyError::InvalidRamK(_, _)) + )); + + let (verifier_preprocessing, mut oversized, program_io) = prove_fibonacci(9); + oversized.ram_K = max_ram_k * 2; + assert!(matches!( + RV64IMACVerifier::new(&verifier_preprocessing, oversized, program_io, None, None), + Err(ProofVerifyError::RamKTooLarge(_, _)) + )); + } + + #[test] + #[serial] + fn verifier_rejects_tampered_dory_layout() { + let (verifier_preprocessing, mut proof, program_io) = prove_fibonacci(9); + proof.dory_layout = match proof.dory_layout { + DoryLayout::CycleMajor => DoryLayout::AddressMajor, + DoryLayout::AddressMajor => DoryLayout::CycleMajor, + }; + + let verifier = + RV64IMACVerifier::new(&verifier_preprocessing, proof, program_io, None, None).unwrap(); + assert!(verifier.verify().is_err()); + } + + #[test] + #[serial] + fn verifier_rejects_tampered_rw_config() { + let (verifier_preprocessing, mut proof, program_io) = prove_fibonacci(9); + proof.rw_config.registers_rw_phase1_num_rounds = 0; + + let verifier = + RV64IMACVerifier::new(&verifier_preprocessing, proof, program_io, None, None).unwrap(); + assert!(verifier.verify().is_err()); + } + /// Security property: the verifier must reject a proof when the verifier's preprocessing /// has a different entry_address than the one used to generate the proof. /// diff --git a/jolt-core/src/zkvm/ram/mod.rs b/jolt-core/src/zkvm/ram/mod.rs index 715714a559..a44789241b 100644 --- a/jolt-core/src/zkvm/ram/mod.rs +++ b/jolt-core/src/zkvm/ram/mod.rs @@ -142,6 +142,20 @@ pub fn compute_min_ram_K( bytecode_end.max(io_end).next_power_of_two() } +/// Computes the maximum valid `ram_K` from the public memory layout. +/// +/// `ram_K` only needs to cover the address interval from the lowest mapped I/O +/// word up to the exclusive end of the heap/stack/program region. +pub fn compute_max_ram_K(memory_layout: &MemoryLayout) -> usize { + let lowest_address = memory_layout.get_lowest_address(); + let address_words = memory_layout + .heap_end + .checked_sub(lowest_address) + .expect("heap_end must be above lowest mapped address") + .div_ceil(8) as usize; + address_words.max(1).next_power_of_two() +} + /// Returns Some(address) if there was read/write /// Returns None if there was no read/write #[inline(always)] diff --git a/jolt-core/src/zkvm/verifier.rs b/jolt-core/src/zkvm/verifier.rs index b961b377dc..24c2c0f315 100644 --- a/jolt-core/src/zkvm/verifier.rs +++ b/jolt-core/src/zkvm/verifier.rs @@ -54,7 +54,8 @@ use crate::zkvm::{ proof_serialization::JoltProof, r1cs::key::UniformSpartanKey, ram::{ - compute_min_ram_K, hamming_booleanity::HammingBooleanitySumcheckVerifier, + compute_max_ram_K, compute_min_ram_K, + hamming_booleanity::HammingBooleanitySumcheckVerifier, output_check::OutputSumcheckVerifier, ra_virtual::RamRaVirtualSumcheckVerifier, raf_evaluation::RafEvaluationSumcheckVerifier as RamRafEvaluationSumcheckVerifier, read_write_checking::RamReadWriteCheckingVerifier, val_check::RamValCheckSumcheckVerifier, @@ -262,14 +263,22 @@ impl< ); let zk_mode = proof.stage1_sumcheck_proof.is_zk(); + if proof.trace_length == 0 || !proof.trace_length.is_power_of_two() { + return Err(ProofVerifyError::InvalidTraceLength(proof.trace_length)); + } + if proof.trace_length > preprocessing.shared.max_padded_trace_length { + return Err(ProofVerifyError::TraceLengthTooLarge( + proof.trace_length, + preprocessing.shared.max_padded_trace_length, + )); + } + let log_trace_length = proof.trace_length.log_2(); #[cfg(test)] #[allow(unused_mut)] - let mut opening_accumulator = - VerifierOpeningAccumulator::new(proof.trace_length.log_2(), zk_mode); + let mut opening_accumulator = VerifierOpeningAccumulator::new(log_trace_length, zk_mode); #[cfg(not(test))] #[allow(unused_mut)] - let mut opening_accumulator = - VerifierOpeningAccumulator::new(proof.trace_length.log_2(), zk_mode); + let mut opening_accumulator = VerifierOpeningAccumulator::new(log_trace_length, zk_mode); #[cfg(not(feature = "zk"))] { @@ -295,7 +304,7 @@ impl< } } - let spartan_key = UniformSpartanKey::new(proof.trace_length.next_power_of_two()); + let spartan_key = UniformSpartanKey::new(proof.trace_length); // Validate configs from the proof proof @@ -307,13 +316,17 @@ impl< &preprocessing.shared.ram, &preprocessing.shared.memory_layout, ); - if !proof.ram_K.is_power_of_two() || proof.ram_K < min_ram_K { + if proof.ram_K == 0 || !proof.ram_K.is_power_of_two() || proof.ram_K < min_ram_K { return Err(ProofVerifyError::InvalidRamK(proof.ram_K, min_ram_K)); } + let max_ram_K = compute_max_ram_K(&preprocessing.shared.memory_layout); + if proof.ram_K > max_ram_K { + return Err(ProofVerifyError::RamKTooLarge(proof.ram_K, max_ram_K)); + } proof .rw_config - .validate(proof.trace_length.log_2(), proof.ram_K.log_2()) + .validate(log_trace_length, proof.ram_K.log_2()) .map_err(ProofVerifyError::InvalidReadWriteConfig)?; // Construct full params from the validated config. @@ -339,6 +352,7 @@ impl< #[cfg_attr(not(feature = "zk"), allow(unused_variables))] pub fn verify(mut self) -> Result<(), ProofVerifyError> { let _pprof_verify = pprof_scope!("verify"); + let _dory_runtime_guard = DoryGlobals::acquire_runtime_guard(); let zk_mode = self.opening_accumulator.zk_mode; fiat_shamir_preamble( @@ -346,6 +360,9 @@ impl< self.proof.ram_K, self.proof.trace_length, self.preprocessing.shared.bytecode.entry_address, + &self.proof.rw_config, + &self.proof.one_hot_config, + self.proof.dory_layout, &mut self.transcript, ); @@ -368,27 +385,35 @@ impl< let (stage1_result, uniskip_challenge1) = self .verify_stage1() .inspect_err(|e| tracing::error!("Stage 1: {e}"))?; + self.opening_accumulator.take_missing_opening_error()?; let (stage2_result, uniskip_challenge2) = self .verify_stage2() .inspect_err(|e| tracing::error!("Stage 2: {e}"))?; + self.opening_accumulator.take_missing_opening_error()?; let stage3_result = self .verify_stage3() .inspect_err(|e| tracing::error!("Stage 3: {e}"))?; + self.opening_accumulator.take_missing_opening_error()?; let stage4_result = self .verify_stage4() .inspect_err(|e| tracing::error!("Stage 4: {e}"))?; + self.opening_accumulator.take_missing_opening_error()?; let stage5_result = self .verify_stage5() .inspect_err(|e| tracing::error!("Stage 5: {e}"))?; + self.opening_accumulator.take_missing_opening_error()?; let stage6_result = self .verify_stage6() .inspect_err(|e| tracing::error!("Stage 6: {e}"))?; + self.opening_accumulator.take_missing_opening_error()?; let stage7_result = self .verify_stage7() .inspect_err(|e| tracing::error!("Stage 7: {e}"))?; + self.opening_accumulator.take_missing_opening_error()?; let stage8_data = self .verify_stage8() .inspect_err(|e| tracing::error!("Stage 8: {e}"))?; + self.opening_accumulator.take_missing_opening_error()?; if zk_mode { #[cfg(feature = "zk")] @@ -1365,7 +1390,7 @@ impl< // This ensures the verifier uses the same layout as the prover let _guard = DoryGlobals::initialize_context( 1 << self.one_hot_params.log_k_chunk, - self.proof.trace_length.next_power_of_two(), + self.proof.trace_length, DoryContext::Main, Some(self.proof.dory_layout), ); @@ -1461,6 +1486,8 @@ impl< include_untrusted_advice = true; } + self.opening_accumulator.take_missing_opening_error()?; + // 2. Sample gamma and compute powers for RLC let claims: Vec = polynomial_claims.iter().map(|(_, c)| *c).collect(); // In non-ZK mode, absorb claims before sampling gamma for Fiat-Shamir binding.