From 0f53a21d4c13d85ce942b1074ae572e53be3ca19 Mon Sep 17 00:00:00 2001 From: sumchecker <241190306+sumchecker@users.noreply.github.com> Date: Thu, 25 Jun 2026 14:25:00 -0700 Subject: [PATCH 1/7] verifier: close Akita packing ownership gaps Co-authored-by: Cursor --- crates/jolt-akita/src/scheme.rs | 65 +++ crates/jolt-verifier/src/akita.rs | 25 +- crates/jolt-verifier/src/akita_openings.rs | 4 +- crates/jolt-verifier/src/akita_packing.rs | 412 ++++-------------- crates/jolt-verifier/src/akita_validation.rs | 2 +- crates/jolt-verifier/src/akita_validity.rs | 3 +- crates/jolt-verifier/src/compat/audit.rs | 8 +- crates/jolt-verifier/src/lib.rs | 28 +- .../jolt-verifier/src/stages/stage8/verify.rs | 233 ++++++++-- .../tests/akita_prover_support.rs | 24 +- .../tests/statistical_independence/zk.rs | 30 +- 11 files changed, 426 insertions(+), 408 deletions(-) diff --git a/crates/jolt-akita/src/scheme.rs b/crates/jolt-akita/src/scheme.rs index f8e626ed1f..a4aab6d79e 100644 --- a/crates/jolt-akita/src/scheme.rs +++ b/crates/jolt-akita/src/scheme.rs @@ -417,6 +417,71 @@ mod tests { .expect_err("changed direct commitment digest should reject"); } + #[test] + fn sparse_native_polynomial_batch_opening_roundtrips_and_binds_commitment() { + let num_vars = 6; + let setup_params = AkitaSetupParams::new(num_vars, 1, [19; 32]); + let (prover_setup, verifier_setup) = AkitaScheme::setup(setup_params); + let unit_indices = [0, 3, 17]; + let sparse = AkitaSparsePolynomial::from_jolt_unit_indices(num_vars, unit_indices) + .expect("sparse polynomial should build"); + let (commitment, hint) = + AkitaScheme::commit_sparse_polynomial(&prover_setup, [23; 32], &sparse) + .expect("sparse commitment should build"); + let mut dense = vec![AkitaField::zero(); 1 << num_vars]; + for index in unit_indices { + dense[index] = AkitaField::one(); + } + let point = (0..num_vars) + .map(|index| AkitaField::from_u64(index as u64 + 2)) + .collect::>(); + let claim = Polynomial::new(dense).evaluate(&point); + let statement = BatchOpeningStatement { + logical_point: point.clone(), + pcs_point: point, + layout_digest: commitment.layout_digest, + claims: vec![BatchOpeningClaim { + id: (), + relation: (), + commitment: commitment.clone(), + claim, + view: PhysicalView::Direct, + scale: AkitaField::one(), + }], + }; + + let mut prover_transcript = RecordingTranscript::new(b"akita-sparse-native"); + let proof = AkitaScheme::prove_sparse_batch( + &prover_setup, + &mut prover_transcript, + &statement, + &sparse, + hint, + ) + .expect("sparse proof should prove"); + assert_eq!(proof.commitment, commitment); + + let mut verifier_transcript = RecordingTranscript::new(b"akita-sparse-native"); + let _result = AkitaScheme::verify_batch( + &verifier_setup, + &mut verifier_transcript, + &statement, + &proof, + ) + .expect("sparse proof should verify"); + + let mut tampered_proof = proof; + tampered_proof.commitment.layout_digest = [42; 32]; + let mut verifier_transcript = RecordingTranscript::new(b"akita-sparse-native"); + let _error = AkitaScheme::verify_batch( + &verifier_setup, + &mut verifier_transcript, + &statement, + &tampered_proof, + ) + .expect_err("tampered proof commitment should reject"); + } + fn contains_subslice(haystack: &[u8], needle: &[u8]) -> bool { haystack .windows(needle.len()) diff --git a/crates/jolt-verifier/src/akita.rs b/crates/jolt-verifier/src/akita.rs index 62fe60d95a..c3ba17c4f4 100644 --- a/crates/jolt-verifier/src/akita.rs +++ b/crates/jolt-verifier/src/akita.rs @@ -1,7 +1,7 @@ //! Prover-facing helpers for assembling Akita verifier artifacts. use crate::{ - akita_packing::AkitaPackingScheme, + akita_packing::{self, AkitaPackingScheme}, akita_validation::{ validate_akita_advice_commitment_aliases, validate_akita_packing_opening_proof_payload_shape, @@ -19,10 +19,14 @@ use crate::{ VerifierError, }; use common::jolt_device::JoltDevice; -use jolt_akita::{AkitaBatchProof, AkitaCommitment, AkitaField, AkitaProverHint}; +use jolt_akita::{ + AkitaBatchProof, AkitaCommitment, AkitaField, AkitaProverHint, AkitaProverSetup, AkitaScheme, + AkitaVerifierSetup, +}; use jolt_field::{RingAccumulator, WithAccumulator}; use jolt_openings::{ - CommitmentScheme, PackingBatchProof, PackingWitnessLayout, PackingWitnessSource, + PackingBatch, PackingBatchProof, PackingProverSetup, PackingVerifierSetup, + PackingWitnessLayout, PackingWitnessSource, }; use jolt_transcript::Transcript; @@ -41,11 +45,14 @@ pub use crate::akita_witness::{build_akita_packing_jolt_witness, AkitaPackingJol pub type AkitaClearVectorCommitment = ClearOnlyVectorCommitment; pub type AkitaPackingBatchProof = PackingBatchProof; -pub type AkitaPackingProverSetup = ::ProverSetup; -pub type AkitaPackingVerifierSetup = ::VerifierSetup; -pub type AkitaVerifierPreprocessing = - JoltVerifierPreprocessing; -pub type AkitaJoltProof = JoltProof; +pub type AkitaPackingProverSetup = PackingProverSetup; +pub type AkitaPackingVerifierSetup = PackingVerifierSetup; +pub type AkitaVerifierPreprocessing = JoltVerifierPreprocessing< + PackingBatch, + AkitaClearVectorCommitment, +>; +pub type AkitaJoltProof = + JoltProof, AkitaClearVectorCommitment>; #[derive(Clone, Debug)] pub struct AkitaPackingWitnessArtifacts { @@ -98,7 +105,7 @@ where let layout = source.layout().clone(); validate_lattice_packed_witness_layout_config(&protocol, &layout)?; let (commitment, hint) = - AkitaPackingScheme::commit_packing_source(setup, source).map_err(|error| { + akita_packing::commit_packing_source(setup, source).map_err(|error| { VerifierError::LatticePackingCommitmentFailed { reason: error.to_string(), } diff --git a/crates/jolt-verifier/src/akita_openings.rs b/crates/jolt-verifier/src/akita_openings.rs index a677da9dd5..d54fd80608 100644 --- a/crates/jolt-verifier/src/akita_openings.rs +++ b/crates/jolt-verifier/src/akita_openings.rs @@ -3,7 +3,7 @@ use crate::{ AkitaClearVectorCommitment, AkitaJoltProof, AkitaPackingBatchProof, AkitaPackingProverSetup, AkitaPackingWitnessArtifacts, AkitaVerifierPreprocessing, }, - akita_packing::AkitaPackingScheme, + akita_packing::{self, AkitaPackingScheme}, akita_validation::validate_akita_artifacts_for_proof, akita_validity::{attach_akita_packing_validity_proof, prove_akita_jolt_packed_validity}, stages::stage8::{Stage8BatchStatement, Stage8OpeningId}, @@ -67,7 +67,7 @@ where } } - AkitaPackingScheme::prove_packing_source_batch( + akita_packing::prove_packing_source_batch( setup, transcript, statement, diff --git a/crates/jolt-verifier/src/akita_packing.rs b/crates/jolt-verifier/src/akita_packing.rs index 7c1ba50f7a..a94953b8db 100644 --- a/crates/jolt-verifier/src/akita_packing.rs +++ b/crates/jolt-verifier/src/akita_packing.rs @@ -1,279 +1,98 @@ use std::collections::BTreeSet; use jolt_akita::{ - AkitaBatchProof, AkitaCommitment, AkitaField, AkitaHidingCommitment, AkitaProverHint, - AkitaProverSetup, AkitaScheme, AkitaSetupParams, AkitaSparsePolynomial, AkitaVerifierSetup, - AKITA_D, + AkitaBatchProof, AkitaCommitment, AkitaField, AkitaProverHint, AkitaProverSetup, AkitaScheme, + AkitaSparsePolynomial, AKITA_D, }; -use jolt_crypto::Commitment; use jolt_openings::{ has_packing_view, packing_witness_source_polynomial, prove_sparse_packing_reduction, validate_packing_source_dimension, validate_packing_source_layout, validate_packing_statement, - BatchOpeningResult, BatchOpeningScheme, BatchOpeningStatement, CommitmentScheme, OpeningsError, - PackingBatch, PackingBatchProof, PackingProverSetup, PackingSetupParams, PackingSource, - PackingVerifierSetup, PackingWitnessLayout, PackingWitnessSource, ZkBatchOpeningScheme, - ZkOpeningScheme, + BatchOpeningScheme, BatchOpeningStatement, OpeningsError, PackingBatch, PackingBatchProof, + PackingProverSetup, PackingSource, PackingWitnessLayout, PackingWitnessSource, }; -use jolt_poly::{MultilinearPoly, Polynomial}; -use jolt_transcript::{AppendToTranscript, Label, Transcript}; -use serde::{Deserialize, Serialize}; +use jolt_transcript::Transcript; -type AkitaPackingAdapter = PackingBatch; +pub(crate) type AkitaPackingScheme = PackingBatch; +pub(crate) type AkitaPackingBatchProof = PackingBatchProof; +pub(crate) type AkitaPackingProverSetup = + PackingProverSetup; -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct AkitaPackingScheme; - -impl AkitaPackingScheme { - pub fn commit_packing_source( - setup: &::ProverSetup, - source: &S, - ) -> Result<(AkitaCommitment, AkitaProverHint), OpeningsError> - where - S: PackingWitnessSource, - { - validate_packing_source_dimension(setup.pcs.max_num_vars, source.layout())?; - if let Some(polynomial) = packed_source_sparse_polynomial(source)? { - return AkitaScheme::commit_sparse_polynomial( - &setup.pcs, - source.layout().digest, - &polynomial, - ); - } - let polynomial = packing_witness_source_polynomial(source)?; - AkitaScheme::commit_group(&setup.pcs, source.layout().digest, &[polynomial]) - } - - pub fn prove_packing_source_batch( - setup: &::ProverSetup, - transcript: &mut T, - statement: &BatchOpeningStatement, - source: &S, - hint: AkitaProverHint, - ) -> Result, OpeningsError> - where - T: Transcript, - S: PackingWitnessSource, - { - validate_packing_source_dimension(setup.pcs.max_num_vars, source.layout())?; - if let Some(sparse_polynomial) = packed_source_sparse_polynomial(source)? { - if !has_packing_view(statement) { - let native = AkitaScheme::prove_sparse_batch( - &setup.pcs, - transcript, - statement, - &sparse_polynomial, - hint, - )?; - return Ok(PackingBatchProof { - reduction: None, - native, - }); - } +pub(crate) fn commit_packing_source( + setup: &AkitaPackingProverSetup, + source: &S, +) -> Result<(AkitaCommitment, AkitaProverHint), OpeningsError> +where + S: PackingWitnessSource, +{ + validate_packing_source_dimension(setup.pcs.max_num_vars, source.layout())?; + if let Some(polynomial) = packed_source_sparse_polynomial(source)? { + return AkitaScheme::commit_sparse_polynomial( + &setup.pcs, + source.layout().digest, + &polynomial, + ); + } + let polynomial = packing_witness_source_polynomial(source)?; + AkitaScheme::commit_group(&setup.pcs, source.layout().digest, &[polynomial]) +} - let shape = validate_packed_source_prover_inputs(setup, statement, source, &hint)?; - let source = AkitaPackingSource(source); - let reduction = - prove_sparse_packing_reduction(shape.layout, statement, &source, transcript)?; - let native_statement = singleton_statement( - shape.commitment.clone(), - &reduction.opening_point, - reduction.opening_eval, - ); +pub(crate) fn prove_packing_source_batch( + setup: &AkitaPackingProverSetup, + transcript: &mut T, + statement: &BatchOpeningStatement, + source: &S, + hint: AkitaProverHint, +) -> Result +where + T: Transcript, + S: PackingWitnessSource, +{ + validate_packing_source_dimension(setup.pcs.max_num_vars, source.layout())?; + if let Some(sparse_polynomial) = packed_source_sparse_polynomial(source)? { + if !has_packing_view(statement) { let native = AkitaScheme::prove_sparse_batch( &setup.pcs, transcript, - &native_statement, + statement, &sparse_polynomial, hint, )?; return Ok(PackingBatchProof { - reduction: Some(reduction.proof), + reduction: None, native, }); } - let polynomial = packing_witness_source_polynomial(source)?; - ::prove_batch( - setup, - transcript, - statement, - std::slice::from_ref(&polynomial), - vec![hint], - ) - } -} - -impl Commitment for AkitaPackingScheme { - type Output = AkitaCommitment; -} - -impl CommitmentScheme for AkitaPackingScheme { - type Field = AkitaField; - type Proof = PackingBatchProof; - type ProverSetup = PackingProverSetup; - type VerifierSetup = PackingVerifierSetup; - type Polynomial = Polynomial; - type OpeningHint = AkitaProverHint; - type SetupParams = PackingSetupParams; - - fn setup(params: Self::SetupParams) -> (Self::ProverSetup, Self::VerifierSetup) { - ::setup(params) - } - - fn verifier_setup(prover_setup: &Self::ProverSetup) -> Self::VerifierSetup { - ::verifier_setup(prover_setup) - } - - fn commit + ?Sized>( - _poly: &P, - _setup: &Self::ProverSetup, - ) -> (Self::Output, Self::OpeningHint) { - unsupported_dense_packed_path("commit") - } - - fn open( - _poly: &Self::Polynomial, - _point: &[Self::Field], - _eval: Self::Field, - _setup: &Self::ProverSetup, - _hint: Option, - _transcript: &mut impl Transcript, - ) -> Self::Proof { - unsupported_dense_packed_path("open") - } - - fn verify( - commitment: &Self::Output, - point: &[Self::Field], - eval: Self::Field, - proof: &Self::Proof, - setup: &Self::VerifierSetup, - transcript: &mut impl Transcript, - ) -> Result<(), OpeningsError> { - ::verify( - commitment, point, eval, proof, setup, transcript, - ) - } - - fn bind_opening_inputs( - transcript: &mut impl Transcript, - point: &[Self::Field], - eval: &Self::Field, - ) { - AkitaScheme::bind_opening_inputs(transcript, point, eval); - } -} - -impl BatchOpeningScheme for AkitaPackingScheme { - fn prove_batch( - setup: &Self::ProverSetup, - transcript: &mut T, - statement: &BatchOpeningStatement, - polynomials: &[Self::Polynomial], - hints: Vec, - ) -> Result - where - T: Transcript, - { - if has_packing_view(statement) { - validate_packed_adapter_prover_inputs(setup, statement, polynomials, &hints)?; - } - ::prove_batch( - setup, + let shape = validate_packed_source_prover_inputs(setup, statement, source, &hint)?; + let source = AkitaPackingSource(source); + let reduction = + prove_sparse_packing_reduction(shape.layout, statement, &source, transcript)?; + let native_statement = singleton_statement( + shape.commitment.clone(), + &reduction.opening_point, + reduction.opening_eval, + ); + let native = AkitaScheme::prove_sparse_batch( + &setup.pcs, transcript, - statement, - polynomials, - hints, - ) - } - - fn verify_batch( - setup: &Self::VerifierSetup, - transcript: &mut T, - statement: &BatchOpeningStatement, - proof: &Self::Proof, - ) -> Result, OpeningsError> - where - T: Transcript, - { - if has_packing_view(statement) { - validate_packed_adapter_verifier_inputs(setup, statement)?; - } - ::verify_batch( - setup, transcript, statement, proof, - ) - } -} - -impl ZkOpeningScheme for AkitaPackingScheme { - type HidingCommitment = AkitaHidingCommitment; - type Blind = (); - - fn commit_zk + ?Sized>( - _poly: &P, - _setup: &Self::ProverSetup, - ) -> (Self::Output, Self::OpeningHint) { - unsupported_dense_packed_path("commit_zk") - } - - fn open_zk( - _poly: &Self::Polynomial, - _point: &[Self::Field], - _eval: Self::Field, - _setup: &Self::ProverSetup, - _hint: Self::OpeningHint, - _transcript: &mut impl Transcript, - ) -> (Self::Proof, Self::HidingCommitment, Self::Blind) { - unsupported_dense_packed_path("open_zk") - } - - fn verify_zk( - _commitment: &Self::Output, - _point: &[Self::Field], - _proof: &Self::Proof, - _setup: &Self::VerifierSetup, - _transcript: &mut impl Transcript, - ) -> Result { - Err(transparent_zk_error()) - } - - fn bind_zk_opening_inputs( - transcript: &mut impl Transcript, - point: &[Self::Field], - hiding_commitment: &Self::HidingCommitment, - ) { - transcript.append(&Label(b"akpk_zk_inputs")); - append_field_slice(transcript, b"akpk_zk_point", point); - hiding_commitment.append_to_transcript(transcript); - } -} - -impl ZkBatchOpeningScheme for AkitaPackingScheme { - fn prove_batch_zk( - _setup: &Self::ProverSetup, - _transcript: &mut T, - _statement: &BatchOpeningStatement, - _evals: &[Self::Field], - _polynomials: &[Self::Polynomial], - _hints: Vec, - ) -> Result<(Self::Proof, Self::HidingCommitment, Self::Blind), OpeningsError> - where - T: Transcript, - { - Err(transparent_zk_error()) + &native_statement, + &sparse_polynomial, + hint, + )?; + return Ok(PackingBatchProof { + reduction: Some(reduction.proof), + native, + }); } - fn verify_batch_zk( - _setup: &Self::VerifierSetup, - _transcript: &mut T, - _statement: &BatchOpeningStatement, - _proof: &Self::Proof, - ) -> Result, OpeningsError> - where - T: Transcript, - { - Err(transparent_zk_error()) - } + let polynomial = packing_witness_source_polynomial(source)?; + ::prove_batch( + setup, + transcript, + statement, + std::slice::from_ref(&polynomial), + vec![hint], + ) } struct AkitaPackingSource<'a, S>(&'a S); @@ -298,39 +117,23 @@ struct PackingBatchShape<'a> { commitment: AkitaCommitment, } -fn validate_packed_adapter_prover_inputs( - setup: &::ProverSetup, +fn validate_packed_source_prover_inputs<'a, OpeningId, RelationId, S>( + setup: &'a AkitaPackingProverSetup, statement: &BatchOpeningStatement, - polynomials: &[Polynomial], - hints: &[AkitaProverHint], -) -> Result<(), OpeningsError> { + source: &S, + hint: &AkitaProverHint, +) -> Result, OpeningsError> +where + S: PackingWitnessSource, +{ let shape = validate_packed_adapter_statement(&setup.pcs, &setup.layout, statement)?; - if polynomials.len() != 1 { - return Err(invalid_batch(format!( - "Akita packing proof expects one packed witness polynomial, got {}", - polynomials.len() - ))); - } - if polynomials[0].num_vars() != shape.commitment.num_vars { - return Err(invalid_batch(format!( - "Akita packing witness polynomial has {} variables but commitment has {}", - polynomials[0].num_vars(), - shape.commitment.num_vars - ))); - } - if hints.len() != 1 || !hints[0].matches_commitment(&shape.commitment) { + validate_packing_source_layout(shape.layout, source)?; + if !hint.matches_commitment(&shape.commitment) { return Err(invalid_batch( "Akita packing proof requires one hint matching the packed witness commitment", )); } - Ok(()) -} - -fn validate_packed_adapter_verifier_inputs( - setup: &::VerifierSetup, - statement: &BatchOpeningStatement, -) -> Result<(), OpeningsError> { - validate_packed_adapter_statement(&setup.pcs, &setup.layout, statement).map(|_| ()) + Ok(shape) } fn validate_packed_adapter_statement<'a, Setup, OpeningId, RelationId>( @@ -351,25 +154,6 @@ where Ok(PackingBatchShape { layout, commitment }) } -fn validate_packed_source_prover_inputs<'a, OpeningId, RelationId, S>( - setup: &'a ::ProverSetup, - statement: &BatchOpeningStatement, - source: &S, - hint: &AkitaProverHint, -) -> Result, OpeningsError> -where - S: PackingWitnessSource, -{ - let shape = validate_packed_adapter_statement(&setup.pcs, &setup.layout, statement)?; - validate_packing_source_layout(shape.layout, source)?; - if !hint.matches_commitment(&shape.commitment) { - return Err(invalid_batch( - "Akita packing proof requires one hint matching the packed witness commitment", - )); - } - Ok(shape) -} - trait AkitaPackingSetupShape { fn max_num_vars(&self) -> usize; fn default_layout_digest(&self) -> [u8; 32]; @@ -385,16 +169,6 @@ impl AkitaPackingSetupShape for AkitaProverSetup { } } -impl AkitaPackingSetupShape for AkitaVerifierSetup { - fn max_num_vars(&self) -> usize { - self.max_num_vars - } - - fn default_layout_digest(&self) -> [u8; 32] { - self.default_layout_digest - } -} - fn validate_packed_setup_shape( max_num_vars: usize, default_layout_digest: [u8; 32], @@ -520,30 +294,6 @@ fn singleton_statement( } } -#[expect( - clippy::panic, - reason = "single-opening trait methods cannot return unsupported packed dense-path errors" -)] -fn unsupported_dense_packed_path(operation: &str) -> ! { - panic!( - "AkitaPackingScheme::{operation} cannot be used for dense proof-owned polynomials; use commit_packing_source/prove_packing_source_batch for W_pack or AkitaScheme direct openings for precommitted objects" - ) -} - -fn append_field_slice(transcript: &mut T, label: &'static [u8], values: &[AkitaField]) -where - T: Transcript, -{ - transcript.append(&jolt_transcript::LabelWithCount(label, values.len() as u64)); - for value in values { - value.append_to_transcript(transcript); - } -} - fn invalid_batch(reason: impl Into) -> OpeningsError { OpeningsError::InvalidBatch(reason.into()) } - -fn transparent_zk_error() -> OpeningsError { - invalid_batch("Akita packing batch openings do not support ZK mode yet") -} diff --git a/crates/jolt-verifier/src/akita_validation.rs b/crates/jolt-verifier/src/akita_validation.rs index ac159ffb2c..dbcc67e4b3 100644 --- a/crates/jolt-verifier/src/akita_validation.rs +++ b/crates/jolt-verifier/src/akita_validation.rs @@ -412,9 +412,9 @@ mod tests { )] use super::*; + use crate::akita_packing::AkitaPackingScheme; use crate::{ akita::{commit_akita_packing_witness, AkitaPackingProverSetup}, - akita_packing::AkitaPackingScheme, proof::LatticeCommitmentPayload, stages::stage8::lattice_protocol_config_for_packed_witness_layout, }; diff --git a/crates/jolt-verifier/src/akita_validity.rs b/crates/jolt-verifier/src/akita_validity.rs index 2ac4639768..3508302f6c 100644 --- a/crates/jolt-verifier/src/akita_validity.rs +++ b/crates/jolt-verifier/src/akita_validity.rs @@ -1,10 +1,10 @@ +use crate::akita_packing::AkitaPackingScheme; use crate::{ akita::{ prove_akita_packing_openings, AkitaClearVectorCommitment, AkitaJoltProof, AkitaPackingBatchProof, AkitaPackingProverSetup, AkitaPackingWitnessArtifacts, AkitaVerifierPreprocessing, }, - akita_packing::AkitaPackingScheme, akita_validation::validate_akita_artifacts_for_proof, akita_validity_sumcheck::prove_combined_validity_sumcheck, proof::{ClearOnlyCommitment, JoltProofClaims}, @@ -750,7 +750,6 @@ mod tests { use super::*; use crate::{ akita::{commit_akita_packing_witness_with_config, AkitaPackingVerifierSetup}, - akita_packing::AkitaPackingScheme, config::{IncrementCommitmentMode, JoltProtocolConfig, PcsFamily, ProgramMode}, proof::ClearOnlyCommitment, stages::{ diff --git a/crates/jolt-verifier/src/compat/audit.rs b/crates/jolt-verifier/src/compat/audit.rs index 5864dbc515..a73c76baf6 100644 --- a/crates/jolt-verifier/src/compat/audit.rs +++ b/crates/jolt-verifier/src/compat/audit.rs @@ -4,7 +4,9 @@ use common::jolt_device::JoltDevice; use jolt_blindfold::BlindFoldProtocol; use jolt_crypto::{HomomorphicCommitment, VectorCommitment}; use jolt_field::Field; -use jolt_openings::{BatchOpeningScheme, CommitmentScheme, ZkBatchOpeningScheme}; +use jolt_openings::{ + BatchOpeningScheme, CommitmentLayoutDigest, CommitmentScheme, ZkBatchOpeningScheme, +}; use jolt_transcript::{AppendToTranscript, Transcript}; use crate::{ @@ -60,7 +62,9 @@ where PCS: CommitmentScheme + BatchOpeningScheme + ZkBatchOpeningScheme, - PCS::Output: AppendToTranscript, + // Stage 8 still uses shared clear/ZK statement construction; splitting a + // ZK-only builder would be broader than this audit helper. + PCS::Output: AppendToTranscript + CommitmentLayoutDigest, VC: VectorCommitment, VC::Output: Copy + HomomorphicCommitment + AppendToTranscript, T: Transcript, diff --git a/crates/jolt-verifier/src/lib.rs b/crates/jolt-verifier/src/lib.rs index 2aa47a5e55..8fb5a0f1da 100644 --- a/crates/jolt-verifier/src/lib.rs +++ b/crates/jolt-verifier/src/lib.rs @@ -1,7 +1,7 @@ //! Verifier model crate for Jolt proofs. #[cfg(feature = "akita")] -pub mod akita; +mod akita; #[cfg(feature = "akita")] mod akita_openings; #[cfg(feature = "akita")] @@ -25,16 +25,24 @@ mod verifier; #[cfg(feature = "akita")] pub use akita::{ - attach_akita_packing_validity_proof, commit_akita_packing_witness, - commit_akita_packing_witness_with_config, prove_akita_jolt_final_openings, - prove_akita_jolt_final_openings_with_precommitted, prove_akita_jolt_packed_validity, - prove_akita_packing_openings, prove_akita_packing_validity, prove_akita_stage8_clear_openings, - prove_akita_stage8_clear_openings_with_precommitted, prove_and_attach_akita_opening_proofs, - prove_and_attach_akita_opening_proofs_with_precommitted, verify_akita_clear, - AkitaClearVectorCommitment, AkitaJoltProof, AkitaPackingValidityProofArtifacts, - AkitaPackingWitnessArtifacts, AkitaPrecommittedOpeningInput, AkitaStage8ClearOpeningProofs, - AkitaVerifierPreprocessing, + verify_akita_clear, AkitaClearVectorCommitment, AkitaJoltProof, AkitaVerifierPreprocessing, }; +#[cfg(feature = "akita")] +pub mod prover_support { + pub use crate::akita::{ + attach_akita_packing_validity_proof, build_akita_packing_jolt_witness, + commit_akita_packing_jolt_witness, commit_akita_packing_witness, + commit_akita_packing_witness_with_config, prove_akita_jolt_final_openings, + prove_akita_jolt_final_openings_with_precommitted, prove_akita_jolt_packed_validity, + prove_akita_packing_openings, prove_akita_packing_validity, + prove_akita_stage8_clear_openings, prove_akita_stage8_clear_openings_with_precommitted, + prove_and_attach_akita_opening_proofs, + prove_and_attach_akita_opening_proofs_with_precommitted, AkitaCommittedPackedJoltWitness, + AkitaPackingBatchProof, AkitaPackingJoltWitnessInput, AkitaPackingProverSetup, + AkitaPackingValidityProofArtifacts, AkitaPackingVerifierSetup, + AkitaPackingWitnessArtifacts, AkitaPrecommittedOpeningInput, AkitaStage8ClearOpeningProofs, + }; +} pub use config::{ validate_proof_config, validate_protocol_config, AdviceLatticeConfig, FieldInlineLatticeConfig, IncrementCommitmentMode, JoltProtocolConfig, LatticeConfig, PackedWitnessConfig, PcsFamily, diff --git a/crates/jolt-verifier/src/stages/stage8/verify.rs b/crates/jolt-verifier/src/stages/stage8/verify.rs index d68fea8341..fffbbae39e 100644 --- a/crates/jolt-verifier/src/stages/stage8/verify.rs +++ b/crates/jolt-verifier/src/stages/stage8/verify.rs @@ -125,31 +125,14 @@ where if checked.zk { return Err(VerifierError::ExpectedCommittedProof { field: "stage8" }); } - let batch_result = PCS::verify_batch( + let output = verify_clear_batch_statement::( &preprocessing.pcs_setup, transcript, - &batch.statement, + batch, &proof.joint_opening_proof, - ) - .map_err(|error| VerifierError::FinalOpeningVerificationFailed { - reason: error.to_string(), - })?; - let mut coefficients = batch_result.coefficients; - coefficients.extend(verify_precommitted_opening_batches::( - &preprocessing.pcs_setup, - transcript, - &batch.precommitted_statements, &proof.lattice_precommitted_opening_proofs, - )?); - - Ok(Stage8Output::Clear(Stage8ClearOutput { - opening_claims: batch.opening_claims, - opening_ids: batch.opening_ids, - constraint_coefficients: coefficients, - pcs_opening_point: batch.pcs_opening_point, - joint_claim: batch_result.reduced_opening, - joint_commitment: batch_result.joint_commitment, - })) + )?; + Ok(Stage8Output::Clear(output)) } } } @@ -184,21 +167,48 @@ where return Err(VerifierError::ExpectedClearProof { field: "stage8" }); }; - let batch_result = PCS::verify_batch( + verify_clear_batch_statement::( &preprocessing.pcs_setup, transcript, - &batch.statement, + batch, &proof.joint_opening_proof, + &proof.lattice_precommitted_opening_proofs, ) - .map_err(|error| VerifierError::FinalOpeningVerificationFailed { - reason: error.to_string(), - })?; +} + +fn verify_clear_batch_statement( + setup: &PCS::VerifierSetup, + transcript: &mut T, + batch: Stage8ClearBatchStatement, + proof: &PCS::Proof, + precommitted_proofs: &[PCS::Proof], +) -> Result, VerifierError> +where + PCS: BatchOpeningScheme, + T: Transcript, +{ + if batch.precommitted_statements.len() != precommitted_proofs.len() { + return Err(VerifierError::FinalOpeningVerificationFailed { + reason: format!( + "expected {} precommitted opening proofs, got {}", + batch.precommitted_statements.len(), + precommitted_proofs.len() + ), + }); + } + + let batch_result = + PCS::verify_batch(setup, transcript, &batch.statement, proof).map_err(|error| { + VerifierError::FinalOpeningVerificationFailed { + reason: error.to_string(), + } + })?; let mut coefficients = batch_result.coefficients; coefficients.extend(verify_precommitted_opening_batches::( - &preprocessing.pcs_setup, + setup, transcript, &batch.precommitted_statements, - &proof.lattice_precommitted_opening_proofs, + precommitted_proofs, )?); Ok(Stage8ClearOutput { @@ -1032,6 +1042,173 @@ mod tests { use super::*; use jolt_field::{Fr, FromPrimitiveInt}; + use jolt_openings::{BatchOpeningResult, OpeningsError}; + use jolt_poly::{MultilinearPoly, Polynomial}; + + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + struct MainProofFailingPcs; + + impl jolt_crypto::Commitment for MainProofFailingPcs { + type Output = u64; + } + + impl CommitmentScheme for MainProofFailingPcs { + type Field = Fr; + type Proof = Fr; + type ProverSetup = (); + type VerifierSetup = (); + type Polynomial = Polynomial; + type OpeningHint = (); + type SetupParams = (); + + fn setup(_params: Self::SetupParams) -> (Self::ProverSetup, Self::VerifierSetup) { + ((), ()) + } + + fn verifier_setup(_prover_setup: &Self::ProverSetup) -> Self::VerifierSetup {} + + fn commit + ?Sized>( + _poly: &P, + _setup: &Self::ProverSetup, + ) -> (Self::Output, Self::OpeningHint) { + (0, ()) + } + + fn open( + _poly: &Self::Polynomial, + _point: &[Self::Field], + eval: Self::Field, + _setup: &Self::ProverSetup, + _hint: Option, + _transcript: &mut impl Transcript, + ) -> Self::Proof { + eval + } + + fn verify( + _commitment: &Self::Output, + _point: &[Self::Field], + _eval: Self::Field, + _proof: &Self::Proof, + _setup: &Self::VerifierSetup, + _transcript: &mut impl Transcript, + ) -> Result<(), OpeningsError> { + Ok(()) + } + + fn bind_opening_inputs( + _transcript: &mut impl Transcript, + _point: &[Self::Field], + _eval: &Self::Field, + ) { + } + } + + impl BatchOpeningScheme for MainProofFailingPcs { + fn prove_batch( + _setup: &Self::ProverSetup, + _transcript: &mut T, + statement: &BatchOpeningStatement, + _polynomials: &[Self::Polynomial], + _hints: Vec, + ) -> Result + where + T: Transcript, + { + statement + .claims + .first() + .map(|claim| claim.claim) + .ok_or(OpeningsError::VerificationFailed) + } + + fn verify_batch( + _setup: &Self::VerifierSetup, + _transcript: &mut T, + _statement: &BatchOpeningStatement, + _proof: &Self::Proof, + ) -> Result, OpeningsError> + where + T: Transcript, + { + Err(OpeningsError::InvalidBatch( + "main packed proof was checked first".to_string(), + )) + } + } + + #[test] + fn clear_batch_rejects_precommitted_count_before_main_proof() { + let id = final_opening_id(JoltCommittedPolynomial::RdInc).into(); + let point = vec![Fr::from_u64(2)]; + let statement = BatchOpeningStatement { + logical_point: point.clone(), + pcs_point: point.clone(), + layout_digest: [0; 32], + claims: vec![BatchOpeningClaim { + id, + relation: id, + commitment: 7, + claim: Fr::from_u64(5), + view: PhysicalView::Direct, + scale: Fr::from_u64(1), + }], + }; + let batch = Stage8ClearBatchStatement { + logical_manifest: Stage8LogicalManifest { + openings: vec![Stage8LogicalOpening { + id, + point: point.clone(), + claim: Some(Fr::from_u64(5)), + scale: Fr::from_u64(1), + }], + pcs_opening_point: Point::high_to_low(point.clone()), + }, + physical_manifest: Stage8PhysicalManifest::direct( + &Stage8LogicalManifest { + openings: Vec::new(), + pcs_opening_point: Point::high_to_low(point.clone()), + }, + [0; 32], + ), + opening_ids: vec![id], + opening_claims: Vec::new(), + pcs_opening_point: Point::high_to_low(point.clone()), + statement: statement.clone(), + precommitted_statements: vec![statement], + }; + let mut transcript = jolt_transcript::Blake2bTranscript::new(b"stage8-count-precheck"); + + let error = verify_clear_batch_statement::( + &(), + &mut transcript, + batch.clone(), + &Fr::from_u64(99), + &[], + ) + .expect_err("missing precommitted proof should reject before main packed proof"); + + assert!(matches!( + error, + VerifierError::FinalOpeningVerificationFailed { reason } + if reason.contains("expected 1 precommitted opening proofs, got 0") + )); + + let error = verify_clear_batch_statement::( + &(), + &mut transcript, + batch, + &Fr::from_u64(99), + &[Fr::from_u64(1), Fr::from_u64(2)], + ) + .expect_err("extra precommitted proof should reject before main packed proof"); + + assert!(matches!( + error, + VerifierError::FinalOpeningVerificationFailed { reason } + if reason.contains("expected 1 precommitted opening proofs, got 2") + )); + } #[test] fn committed_program_batch_entries_require_final_openings() { diff --git a/crates/jolt-verifier/tests/akita_prover_support.rs b/crates/jolt-verifier/tests/akita_prover_support.rs index ce17f1da24..d0ba08cbd8 100644 --- a/crates/jolt-verifier/tests/akita_prover_support.rs +++ b/crates/jolt-verifier/tests/akita_prover_support.rs @@ -25,18 +25,19 @@ use jolt_riscv::{ NormalizedOperands, StoreState, }; use jolt_verifier::{ - akita::{ + prover_support::{ build_akita_packing_jolt_witness, commit_akita_packing_jolt_witness, - commit_akita_packing_witness, commit_akita_packing_witness_with_config, verify_akita_clear, - AkitaJoltProof, AkitaPackingBatchProof, AkitaPackingJoltWitnessInput, - AkitaPackingProverSetup, AkitaPackingValidityProofArtifacts, AkitaPackingVerifierSetup, - AkitaPackingWitnessArtifacts, AkitaVerifierPreprocessing, + commit_akita_packing_witness, commit_akita_packing_witness_with_config, + AkitaPackingBatchProof, AkitaPackingJoltWitnessInput, AkitaPackingProverSetup, + AkitaPackingValidityProofArtifacts, AkitaPackingVerifierSetup, + AkitaPackingWitnessArtifacts, }, stages::stage8::{ lattice_protocol_config_for_packed_witness_layout, lattice_validity_requirements_for_packed_witness_layout, }, - IncrementCommitmentMode, JoltProtocolConfig, ProgramMode, VerifierError, + verify_akita_clear, AkitaJoltProof, AkitaVerifierPreprocessing, IncrementCommitmentMode, + JoltProtocolConfig, ProgramMode, VerifierError, }; fn physical(family: JoltPackingFamilyId) -> PackingFamilyId { @@ -480,7 +481,7 @@ fn akita_clear_verifier_surface_is_nameable() { &AkitaPackingWitnessArtifacts, &SparsePackingWitness, ) -> Result; - let _prove: ProveFn = jolt_verifier::akita::prove_akita_jolt_final_openings::< + let _prove: ProveFn = jolt_verifier::prover_support::prove_akita_jolt_final_openings::< TestTranscript, SparsePackingWitness, >; @@ -493,8 +494,9 @@ fn akita_clear_verifier_surface_is_nameable() { &AkitaPackingWitnessArtifacts, &SparsePackingWitness, ) -> Result; - let _prove_validity: ProveValidityFn = jolt_verifier::akita::prove_akita_jolt_packed_validity::< - TestTranscript, - SparsePackingWitness, - >; + let _prove_validity: ProveValidityFn = + jolt_verifier::prover_support::prove_akita_jolt_packed_validity::< + TestTranscript, + SparsePackingWitness, + >; } diff --git a/crates/jolt-verifier/tests/statistical_independence/zk.rs b/crates/jolt-verifier/tests/statistical_independence/zk.rs index 5e5c978952..4c1191fe84 100644 --- a/crates/jolt-verifier/tests/statistical_independence/zk.rs +++ b/crates/jolt-verifier/tests/statistical_independence/zk.rs @@ -135,6 +135,11 @@ impl StableZkProofShape { let JoltProofClaims::Zk { blindfold_proof } = &case.proof.claims else { panic!("ZK statistical fixture must carry a BlindFold proof"); }; + let commitments = case + .proof + .commitments + .as_dory() + .expect("ZK statistical fixture must use Dory commitments"); Self { public_io: case.public_io.clone(), @@ -145,9 +150,9 @@ impl StableZkProofShape { one_hot_config: case.proof.one_hot_config, trace_polynomial_order: case.proof.trace_polynomial_order, commitment_shape: CommitmentShape { - instruction_ra: case.proof.commitments.ra.instruction.len(), - ram_ra: case.proof.commitments.ra.ram.len(), - bytecode_ra: case.proof.commitments.ra.bytecode.len(), + instruction_ra: commitments.ra.instruction.len(), + ram_ra: commitments.ra.ram.len(), + bytecode_ra: commitments.ra.bytecode.len(), has_untrusted_advice: case.proof.untrusted_advice_commitment.is_some(), }, stage_shapes: committed_stage_shapes(&case.proof.stages), @@ -279,15 +284,16 @@ fn collect_jolt_proof_statistics( let JoltProofClaims::Zk { blindfold_proof } = &proof.claims else { panic!("ZK statistical fixture must carry a BlindFold proof"); }; - - tracker.record_append("pcs.commitment.rd_inc", &proof.commitments.rd_inc); - tracker.record_append("pcs.commitment.ram_inc", &proof.commitments.ram_inc); - tracker.record_append_positions( - "pcs.commitment.instruction_ra", - &proof.commitments.ra.instruction, - ); - tracker.record_append_positions("pcs.commitment.ram_ra", &proof.commitments.ra.ram); - tracker.record_append_positions("pcs.commitment.bytecode_ra", &proof.commitments.ra.bytecode); + let commitments = proof + .commitments + .as_dory() + .expect("ZK statistical fixture must use Dory commitments"); + + tracker.record_append("pcs.commitment.rd_inc", &commitments.rd_inc); + tracker.record_append("pcs.commitment.ram_inc", &commitments.ram_inc); + tracker.record_append_positions("pcs.commitment.instruction_ra", &commitments.ra.instruction); + tracker.record_append_positions("pcs.commitment.ram_ra", &commitments.ra.ram); + tracker.record_append_positions("pcs.commitment.bytecode_ra", &commitments.ra.bytecode); if let Some(commitment) = &proof.untrusted_advice_commitment { tracker.record_append("pcs.commitment.untrusted_advice", commitment); } From c6af79d65a17b9c3fa4ca3851467eb9a67ac3cb4 Mon Sep 17 00:00:00 2001 From: sumchecker <241190306+sumchecker@users.noreply.github.com> Date: Thu, 25 Jun 2026 16:56:49 -0700 Subject: [PATCH 2/7] verifier: align Akita precommitted opening semantics Narrow the Akita adapter surface and make lattice precommitted openings verify against their own commitments, points, layout digests, and exact setup dimension. Co-authored-by: Cursor --- crates/jolt-akita/src/lib.rs | 5 +- crates/jolt-akita/src/scheme.rs | 43 ++++- crates/jolt-akita/src/transcript.rs | 7 +- crates/jolt-akita/src/types.rs | 138 +++++++++++--- crates/jolt-akita/src/zk.rs | 8 +- crates/jolt-akita/tests/commit_open_verify.rs | 4 +- crates/jolt-akita/tests/proof_payload.rs | 29 +-- crates/jolt-akita/tests/support/mod.rs | 2 +- crates/jolt-verifier/src/akita_openings.rs | 76 ++++++-- crates/jolt-verifier/src/akita_packing.rs | 82 ++++---- crates/jolt-verifier/src/akita_validation.rs | 179 +++++++++++++++--- crates/jolt-verifier/src/proof.rs | 32 ++++ .../src/stages/stage8/precommitted.rs | 76 +++++--- .../jolt-verifier/src/stages/stage8/verify.rs | 21 +- crates/jolt-verifier/src/verifier.rs | 34 ++-- .../tests/akita_prover_support.rs | 4 +- 16 files changed, 539 insertions(+), 201 deletions(-) diff --git a/crates/jolt-akita/src/lib.rs b/crates/jolt-akita/src/lib.rs index 05c521546e..ce2de628d3 100644 --- a/crates/jolt-akita/src/lib.rs +++ b/crates/jolt-akita/src/lib.rs @@ -12,7 +12,6 @@ mod zk; pub use scheme::AkitaScheme; pub use types::{ - AkitaBatchProof, AkitaCommitment, AkitaConfig, AkitaField, AkitaHidingCommitment, - AkitaLayoutDigest, AkitaProverHint, AkitaProverSetup, AkitaSetupParams, AkitaSparsePolynomial, - AkitaVerifierSetup, AKITA_D, AKITA_FIELD_MODULUS, + AkitaBatchProof, AkitaCommitment, AkitaField, AkitaHidingCommitment, AkitaProverHint, + AkitaProverSetup, AkitaSetupParams, AkitaVerifierSetup, AKITA_FIELD_MODULUS, }; diff --git a/crates/jolt-akita/src/scheme.rs b/crates/jolt-akita/src/scheme.rs index a4aab6d79e..0355f8c89d 100644 --- a/crates/jolt-akita/src/scheme.rs +++ b/crates/jolt-akita/src/scheme.rs @@ -22,6 +22,15 @@ use crate::types::{ pub struct AkitaScheme; impl AkitaScheme { + /// Returns true when the native Akita sparse-ring path can represent a + /// unit-valued sparse polynomial with this multilinear dimension. + pub fn supports_unit_sparse_dimension(num_vars: usize) -> bool { + let Some(domain_size) = 1usize.checked_shl(num_vars as u32) else { + return false; + }; + domain_size >= AKITA_D + } + pub fn commit_group( setup: &AkitaProverSetup, layout_digest: [u8; 32], @@ -39,7 +48,7 @@ impl AkitaScheme { ) } - pub fn commit_sparse_polynomial( + pub(crate) fn commit_sparse_polynomial( setup: &AkitaProverSetup, layout_digest: [u8; 32], polynomial: &AkitaSparsePolynomial, @@ -53,7 +62,20 @@ impl AkitaScheme { ) } - pub fn prove_sparse_batch( + /// Commit a unit-valued sparse polynomial without materializing dense + /// evaluations. This exposes the native Akita sparse-ring path; ordinary + /// PCS callers should use [`CommitmentScheme::commit`]. + pub fn commit_unit_sparse_indices( + setup: &AkitaProverSetup, + layout_digest: [u8; 32], + num_vars: usize, + indices: impl IntoIterator, + ) -> Result<(AkitaCommitment, AkitaProverHint), OpeningsError> { + let polynomial = AkitaSparsePolynomial::from_jolt_unit_indices(num_vars, indices)?; + Self::commit_sparse_polynomial(setup, layout_digest, &polynomial) + } + + pub(crate) fn prove_sparse_batch( setup: &AkitaProverSetup, transcript: &mut T, statement: &BatchOpeningStatement, @@ -71,6 +93,23 @@ impl AkitaScheme { vec![hint], ) } + + /// Prove a direct batch opening for a unit-valued sparse polynomial without + /// materializing dense evaluations. + pub fn prove_unit_sparse_batch( + setup: &AkitaProverSetup, + transcript: &mut T, + statement: &BatchOpeningStatement, + num_vars: usize, + indices: impl IntoIterator, + hint: AkitaProverHint, + ) -> Result + where + T: Transcript, + { + let polynomial = AkitaSparsePolynomial::from_jolt_unit_indices(num_vars, indices)?; + Self::prove_sparse_batch(setup, transcript, statement, &polynomial, hint) + } } fn commit_native_group

( diff --git a/crates/jolt-akita/src/transcript.rs b/crates/jolt-akita/src/transcript.rs index 88fc4b4503..7d2b00f5d8 100644 --- a/crates/jolt-akita/src/transcript.rs +++ b/crates/jolt-akita/src/transcript.rs @@ -4,7 +4,10 @@ use jolt_transcript::{AppendToTranscript, Label, LabelWithCount, Transcript, U64 use crate::{ native::field_bytes, - types::{append_field_slice, AkitaBatchProof, AkitaCommitment, AkitaField, AkitaVerifierSetup}, + types::{ + append_field_slice, AkitaBatchProof, AkitaCommitment, AkitaField, AkitaVerifierSetup, + AKITA_D, + }, }; /// Bind native Akita setup metadata before any statement-specific challenge. @@ -17,7 +20,7 @@ where { transcript.append(&Label(b"akita_setup_key")); transcript.append_bytes(b"akita/fp128/d64full"); - transcript.append(&U64Word(crate::AKITA_D as u64)); + transcript.append(&U64Word(AKITA_D as u64)); transcript.append(&U64Word(setup.max_num_vars as u64)); transcript.append(&U64Word(setup.max_num_polys_per_commitment_group as u64)); transcript.append_bytes(&setup.default_layout_digest); diff --git a/crates/jolt-akita/src/types.rs b/crates/jolt-akita/src/types.rs index 7ba27cbb6f..ca8c14ff35 100644 --- a/crates/jolt-akita/src/types.rs +++ b/crates/jolt-akita/src/types.rs @@ -14,8 +14,8 @@ use jolt_transcript::{AppendToTranscript, Label, LabelWithCount, Transcript, U64 use serde::{Deserialize, Serialize}; pub type AkitaField = akita_config::proof_optimized::fp128::Field; -pub type AkitaConfig = akita_config::proof_optimized::fp128::D64Full; -pub const AKITA_D: usize = AkitaConfig::D; +pub(crate) type AkitaConfig = akita_config::proof_optimized::fp128::D64Full; +pub(crate) const AKITA_D: usize = AkitaConfig::D; pub const AKITA_FIELD_MODULUS: u128 = u128::MAX - (::MODULUS_OFFSET - 1); @@ -29,14 +29,14 @@ pub(crate) type NativeDensePoly = DensePoly; pub(crate) type NativeSparsePoly = SparseRingPoly; pub(crate) type NativePreparedSetup = CpuPreparedSetup; -pub type AkitaLayoutDigest = [u8; 32]; +pub(crate) type AkitaLayoutDigest = [u8; 32]; -pub struct AkitaSparsePolynomial { +pub(crate) struct AkitaSparsePolynomial { pub(crate) native: NativeSparsePoly, } impl AkitaSparsePolynomial { - pub fn from_jolt_unit_indices( + pub(crate) fn from_jolt_unit_indices( num_vars: usize, indices: impl IntoIterator, ) -> Result { @@ -78,7 +78,7 @@ impl AkitaSparsePolynomial { }) } - pub fn num_vars(&self) -> usize { + pub(crate) fn num_vars(&self) -> usize { self.native.num_vars() } } @@ -97,9 +97,9 @@ fn invalid_sparse_polynomial(reason: impl Into) -> OpeningsError { #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct AkitaSetupParams { - pub max_num_vars: usize, - pub max_num_polys_per_commitment_group: usize, - pub default_layout_digest: AkitaLayoutDigest, + pub(crate) max_num_vars: usize, + pub(crate) max_num_polys_per_commitment_group: usize, + pub(crate) default_layout_digest: AkitaLayoutDigest, } impl AkitaSetupParams { @@ -114,34 +114,96 @@ impl AkitaSetupParams { default_layout_digest, } } + + pub fn max_num_vars(&self) -> usize { + self.max_num_vars + } + + pub fn max_num_polys_per_commitment_group(&self) -> usize { + self.max_num_polys_per_commitment_group + } + + pub fn default_layout_digest(&self) -> [u8; 32] { + self.default_layout_digest + } } #[derive(Clone, Debug)] pub struct AkitaProverSetup { - pub max_num_vars: usize, - pub max_num_polys_per_commitment_group: usize, - pub default_layout_digest: AkitaLayoutDigest, + pub(crate) max_num_vars: usize, + pub(crate) max_num_polys_per_commitment_group: usize, + pub(crate) default_layout_digest: AkitaLayoutDigest, pub(crate) native: akita_prover::AkitaProverSetup, pub(crate) prepared: NativePreparedSetup, pub(crate) verifier: AkitaVerifierSetup, } +impl AkitaProverSetup { + pub fn max_num_vars(&self) -> usize { + self.max_num_vars + } + + pub fn max_num_polys_per_commitment_group(&self) -> usize { + self.max_num_polys_per_commitment_group + } + + pub fn default_layout_digest(&self) -> [u8; 32] { + self.default_layout_digest + } +} + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct AkitaVerifierSetup { - pub max_num_vars: usize, - pub max_num_polys_per_commitment_group: usize, - pub default_layout_digest: AkitaLayoutDigest, - pub native: Vec, + pub(crate) max_num_vars: usize, + pub(crate) max_num_polys_per_commitment_group: usize, + pub(crate) default_layout_digest: AkitaLayoutDigest, + pub(crate) native: Vec, +} + +impl AkitaVerifierSetup { + pub fn max_num_vars(&self) -> usize { + self.max_num_vars + } + + pub fn max_num_polys_per_commitment_group(&self) -> usize { + self.max_num_polys_per_commitment_group + } + + pub fn default_layout_digest(&self) -> [u8; 32] { + self.default_layout_digest + } + + pub fn native_bytes(&self) -> &[u8] { + &self.native + } } #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct AkitaCommitment { - pub layout_digest: AkitaLayoutDigest, - pub num_vars: usize, - pub poly_count: usize, - pub native: Vec, + pub(crate) layout_digest: AkitaLayoutDigest, + pub(crate) num_vars: usize, + pub(crate) poly_count: usize, + pub(crate) native: Vec, +} + +impl AkitaCommitment { + pub fn layout_digest(&self) -> [u8; 32] { + self.layout_digest + } + + pub fn num_vars(&self) -> usize { + self.num_vars + } + + pub fn poly_count(&self) -> usize { + self.poly_count + } + + pub fn native_bytes(&self) -> &[u8] { + &self.native + } } impl AppendToTranscript for AkitaCommitment { @@ -167,16 +229,40 @@ impl CommitmentLayoutDigest for AkitaCommitment { #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct AkitaBatchProof { - pub commitment: AkitaCommitment, - pub statement_bridge: Vec, - pub proof_shape: Vec, - pub proof: Vec, + pub(crate) commitment: AkitaCommitment, + pub(crate) statement_bridge: Vec, + pub(crate) proof_shape: Vec, + pub(crate) proof: Vec, +} + +impl AkitaBatchProof { + pub fn commitment(&self) -> &AkitaCommitment { + &self.commitment + } + + pub fn statement_bridge(&self) -> &[u8] { + &self.statement_bridge + } + + pub fn proof_shape(&self) -> &[u8] { + &self.proof_shape + } + + pub fn proof_bytes(&self) -> &[u8] { + &self.proof + } } #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct AkitaHidingCommitment { - pub eval: Vec, + pub(crate) eval: Vec, +} + +impl AkitaHidingCommitment { + pub(crate) fn new(eval: Vec) -> Self { + Self { eval } + } } impl AppendToTranscript for AkitaHidingCommitment { @@ -192,7 +278,7 @@ impl AppendToTranscript for AkitaHidingCommitment { #[derive(Clone, Debug, Default)] pub struct AkitaProverHint { - pub commitment: AkitaCommitment, + pub(crate) commitment: AkitaCommitment, pub(crate) native: Option, } diff --git a/crates/jolt-akita/src/zk.rs b/crates/jolt-akita/src/zk.rs index 239d170d35..8a1fb2ccda 100644 --- a/crates/jolt-akita/src/zk.rs +++ b/crates/jolt-akita/src/zk.rs @@ -31,13 +31,7 @@ impl ZkOpeningScheme for AkitaScheme { transcript: &mut impl Transcript, ) -> (Self::Proof, Self::HidingCommitment, Self::Blind) { let proof = Self::open(poly, point, eval, setup, Some(hint), transcript); - ( - proof, - AkitaHidingCommitment { - eval: field_bytes(eval), - }, - (), - ) + (proof, AkitaHidingCommitment::new(field_bytes(eval)), ()) } fn verify_zk( diff --git a/crates/jolt-akita/tests/commit_open_verify.rs b/crates/jolt-akita/tests/commit_open_verify.rs index c725280f0c..b2cbe25deb 100644 --- a/crates/jolt-akita/tests/commit_open_verify.rs +++ b/crates/jolt-akita/tests/commit_open_verify.rs @@ -114,7 +114,7 @@ fn akita_direct_commit_group_accepts_statement_layout_and_rejects_dimension_mism let (prover_setup, _) = setup(); let statement_layout = AkitaScheme::commit_group(&prover_setup, layout(8), &[polynomial(1)]) .expect("direct commitments carry their statement layout digest"); - assert_eq!(statement_layout.0.layout_digest, layout(8)); + assert_eq!(statement_layout.0.layout_digest(), layout(8)); let (wrong_dimension_setup, _) = AkitaScheme::setup(AkitaSetupParams::new(5, 2, layout(7))); let wrong_dimension = @@ -140,7 +140,7 @@ fn akita_direct_opening_uses_commitment_layout_digest() { std::slice::from_ref(&poly), ) .expect("direct commitment should commit with its own layout digest"); - assert_eq!(commitment.layout_digest, commitment_layout); + assert_eq!(commitment.layout_digest(), commitment_layout); let statement = BatchOpeningStatement { logical_point: point.clone(), pcs_point: point.clone(), diff --git a/crates/jolt-akita/tests/proof_payload.rs b/crates/jolt-akita/tests/proof_payload.rs index 17ff4ec425..cb690d4b2f 100644 --- a/crates/jolt-akita/tests/proof_payload.rs +++ b/crates/jolt-akita/tests/proof_payload.rs @@ -1,24 +1,29 @@ #![expect(clippy::expect_used, reason = "tests assert serialization shape")] -use jolt_akita::{AkitaBatchProof, AkitaCommitment}; +use jolt_akita::AkitaBatchProof; fn layout(byte: u8) -> [u8; 32] { [byte; 32] } +fn proof_payload() -> AkitaBatchProof { + serde_json::from_value(serde_json::json!({ + "commitment": { + "layout_digest": layout(7), + "num_vars": 4, + "poly_count": 1, + "native": [1, 2, 3], + }, + "statement_bridge": [4], + "proof_shape": [5], + "proof": [6], + })) + .expect("proof payload should deserialize") +} + #[test] fn akita_proof_payloads_reject_unknown_serialized_fields() { - let proof = AkitaBatchProof { - commitment: AkitaCommitment { - layout_digest: layout(7), - num_vars: 4, - poly_count: 1, - native: vec![1, 2, 3], - }, - statement_bridge: vec![4], - proof_shape: vec![5], - proof: vec![6], - }; + let proof = proof_payload(); let mut root = serde_json::to_value(&proof).expect("proof should serialize"); let _ = root diff --git a/crates/jolt-akita/tests/support/mod.rs b/crates/jolt-akita/tests/support/mod.rs index 8514050695..cb51cb6bfb 100644 --- a/crates/jolt-akita/tests/support/mod.rs +++ b/crates/jolt-akita/tests/support/mod.rs @@ -41,7 +41,7 @@ pub fn direct_statement( BatchOpeningStatement { logical_point: point.to_vec(), pcs_point: point.to_vec(), - layout_digest: commitment.layout_digest, + layout_digest: commitment.layout_digest(), claims: vec![ BatchOpeningClaim { id: OpeningId::A, diff --git a/crates/jolt-verifier/src/akita_openings.rs b/crates/jolt-verifier/src/akita_openings.rs index d54fd80608..8637e2ba33 100644 --- a/crates/jolt-verifier/src/akita_openings.rs +++ b/crates/jolt-verifier/src/akita_openings.rs @@ -129,8 +129,6 @@ where &statement.precommitted_statements, precommitted_inputs, )?; - let packed = - prove_akita_packing_openings(setup, transcript, artifacts, source, &statement.statement)?; let precommitted = prove_akita_precommitted_opening_batches( setup, transcript, @@ -138,6 +136,8 @@ where &statement.precommitted_statements, precommitted_inputs, )?; + let packed = + prove_akita_packing_openings(setup, transcript, artifacts, source, &statement.statement)?; Ok(AkitaStage8ClearOpeningProofs { packed, precommitted, @@ -427,7 +427,7 @@ mod tests { Stage8ClearBatchStatement, Stage8LogicalManifest, Stage8PhysicalManifest, }, }; - use jolt_akita::{AkitaScheme, AkitaSetupParams, AKITA_FIELD_MODULUS}; + use jolt_akita::{AkitaBatchProof, AkitaScheme, AkitaSetupParams, AKITA_FIELD_MODULUS}; use jolt_claims::protocols::jolt::{ unsigned_inc_msb_opening, JoltCommittedPolynomial, JoltOpeningId, JoltPackingFamilyId, JoltRelationId, @@ -485,6 +485,38 @@ mod tests { } } + fn commitment_with_layout_digest( + commitment: &AkitaCommitment, + layout_digest: [u8; 32], + ) -> AkitaCommitment { + let mut value = serde_json::to_value(commitment).expect("commitment should serialize"); + let object = value + .as_object_mut() + .expect("commitment should serialize as an object"); + let _ = object.insert( + "layout_digest".to_string(), + serde_json::json!(layout_digest), + ); + serde_json::from_value(value).expect("tampered commitment should deserialize") + } + + fn proof_with_parts( + proof: &AkitaBatchProof, + commitment: AkitaCommitment, + proof_bytes: Vec, + ) -> AkitaBatchProof { + let mut value = serde_json::to_value(proof).expect("proof should serialize"); + let object = value + .as_object_mut() + .expect("proof should serialize as an object"); + let _ = object.insert( + "commitment".to_string(), + serde_json::to_value(commitment).expect("commitment should serialize"), + ); + let _ = object.insert("proof".to_string(), serde_json::json!(proof_bytes)); + serde_json::from_value(value).expect("tampered proof should deserialize") + } + fn akita_packing_params( layout: &jolt_openings::PackingWitnessLayout, max_num_polys_per_commitment_group: usize, @@ -601,7 +633,8 @@ mod tests { let Stage8BatchStatement::Clear(wrong_statement) = &mut wrong_stage8_statement else { unreachable!("test statement is clear"); }; - wrong_statement.statement.claims[0].commitment.layout_digest = [9; 32]; + wrong_statement.statement.claims[0].commitment = + commitment_with_layout_digest(&wrong_statement.statement.claims[0].commitment, [9; 32]); let mut wrong_transcript = Blake2bTranscript::new(b"verifier-akita-packed"); let error = prove_akita_stage8_clear_openings( &prover_setup, @@ -617,7 +650,11 @@ mod tests { )); let mut wrong_commitment_proof = proof.clone(); - wrong_commitment_proof.native.commitment.layout_digest = [9; 32]; + wrong_commitment_proof.native = proof_with_parts( + &wrong_commitment_proof.native, + commitment_with_layout_digest(wrong_commitment_proof.native.commitment(), [9; 32]), + wrong_commitment_proof.native.proof_bytes().to_vec(), + ); assert!(matches!( validate_akita_opening_proof_payload_shape( &artifact.commitments, @@ -628,7 +665,11 @@ mod tests { )); let mut missing_native_proof = proof.clone(); - missing_native_proof.native.proof.clear(); + missing_native_proof.native = proof_with_parts( + &missing_native_proof.native, + missing_native_proof.native.commitment().clone(), + Vec::new(), + ); assert!(matches!( validate_akita_opening_proof_payload_shape(&artifact.commitments, &missing_native_proof), Err(VerifierError::InvalidProtocolConfig { reason }) @@ -769,7 +810,7 @@ mod tests { }; assert_eq!( precommitted_statement.layout_digest, - precommitted_commitment.layout_digest + precommitted_commitment.layout_digest() ); let stage8_statement = Stage8BatchStatement::Clear(Stage8ClearBatchStatement { logical_manifest: Stage8LogicalManifest { @@ -809,7 +850,14 @@ mod tests { .expect("fresh precommitted proof payload should pass preflight"); let mut packed_target_precommitted_proof = proofs.precommitted[0].clone(); - packed_target_precommitted_proof.native.commitment = packed_commitment.clone(); + packed_target_precommitted_proof.native = proof_with_parts( + &packed_target_precommitted_proof.native, + packed_commitment.clone(), + packed_target_precommitted_proof + .native + .proof_bytes() + .to_vec(), + ); assert!(matches!( validate_akita_precommitted_opening_proof_payload_shapes( &artifact.commitments, @@ -878,17 +926,17 @@ mod tests { let _ = ::verify_batch( &verifier_setup, &mut verifier_transcript, - &packed_statement, - &proofs.packed, + &precommitted_statement, + &proofs.precommitted[0], ) - .expect("packed proof should verify"); + .expect("precommitted proof should verify"); let _ = ::verify_batch( &verifier_setup, &mut verifier_transcript, - &precommitted_statement, - &proofs.precommitted[0], + &packed_statement, + &proofs.packed, ) - .expect("precommitted proof should verify"); + .expect("packed proof should verify"); assert_eq!(prover_transcript.state(), verifier_transcript.state()); let mut missing_input_transcript = Blake2bTranscript::new(b"verifier-akita-precommitted"); diff --git a/crates/jolt-verifier/src/akita_packing.rs b/crates/jolt-verifier/src/akita_packing.rs index a94953b8db..ecaa5032c7 100644 --- a/crates/jolt-verifier/src/akita_packing.rs +++ b/crates/jolt-verifier/src/akita_packing.rs @@ -2,7 +2,6 @@ use std::collections::BTreeSet; use jolt_akita::{ AkitaBatchProof, AkitaCommitment, AkitaField, AkitaProverHint, AkitaProverSetup, AkitaScheme, - AkitaSparsePolynomial, AKITA_D, }; use jolt_openings::{ has_packing_view, packing_witness_source_polynomial, prove_sparse_packing_reduction, @@ -24,12 +23,13 @@ pub(crate) fn commit_packing_source( where S: PackingWitnessSource, { - validate_packing_source_dimension(setup.pcs.max_num_vars, source.layout())?; - if let Some(polynomial) = packed_source_sparse_polynomial(source)? { - return AkitaScheme::commit_sparse_polynomial( + validate_packing_source_dimension(setup.pcs.max_num_vars(), source.layout())?; + if let Some(unit_indices) = packed_source_unit_indices(source)? { + return AkitaScheme::commit_unit_sparse_indices( &setup.pcs, source.layout().digest, - &polynomial, + source.layout().dimension, + unit_indices, ); } let polynomial = packing_witness_source_polynomial(source)?; @@ -47,14 +47,15 @@ where T: Transcript, S: PackingWitnessSource, { - validate_packing_source_dimension(setup.pcs.max_num_vars, source.layout())?; - if let Some(sparse_polynomial) = packed_source_sparse_polynomial(source)? { + validate_packing_source_dimension(setup.pcs.max_num_vars(), source.layout())?; + if let Some(unit_indices) = packed_source_unit_indices(source)? { if !has_packing_view(statement) { - let native = AkitaScheme::prove_sparse_batch( + let native = AkitaScheme::prove_unit_sparse_batch( &setup.pcs, transcript, statement, - &sparse_polynomial, + source.layout().dimension, + unit_indices, hint, )?; return Ok(PackingBatchProof { @@ -72,11 +73,12 @@ where &reduction.opening_point, reduction.opening_eval, ); - let native = AkitaScheme::prove_sparse_batch( + let native = AkitaScheme::prove_unit_sparse_batch( &setup.pcs, transcript, &native_statement, - &sparse_polynomial, + source.layout().dimension, + unit_indices, hint, )?; return Ok(PackingBatchProof { @@ -136,14 +138,11 @@ where Ok(shape) } -fn validate_packed_adapter_statement<'a, Setup, OpeningId, RelationId>( - setup: &Setup, +fn validate_packed_adapter_statement<'a, OpeningId, RelationId>( + setup: &AkitaProverSetup, layout: &'a PackingWitnessLayout, statement: &BatchOpeningStatement, -) -> Result, OpeningsError> -where - Setup: AkitaPackingSetupShape, -{ +) -> Result, OpeningsError> { let commitment = validate_packing_statement(layout, statement)?; validate_packed_setup_shape( setup.max_num_vars(), @@ -154,67 +153,52 @@ where Ok(PackingBatchShape { layout, commitment }) } -trait AkitaPackingSetupShape { - fn max_num_vars(&self) -> usize; - fn default_layout_digest(&self) -> [u8; 32]; -} - -impl AkitaPackingSetupShape for AkitaProverSetup { - fn max_num_vars(&self) -> usize { - self.max_num_vars - } - - fn default_layout_digest(&self) -> [u8; 32] { - self.default_layout_digest - } -} - fn validate_packed_setup_shape( max_num_vars: usize, default_layout_digest: [u8; 32], layout: &PackingWitnessLayout, commitment: &AkitaCommitment, ) -> Result<(), OpeningsError> { - if commitment.num_vars > max_num_vars { + if commitment.num_vars() > max_num_vars { return Err(OpeningsError::PolynomialTooLarge { - poly_size: commitment.num_vars, + poly_size: commitment.num_vars(), setup_max: max_num_vars, }); } - if commitment.num_vars != max_num_vars { + if commitment.num_vars() != max_num_vars { return Err(invalid_batch(format!( "Akita packing commitment dimension {} does not match exact setup dimension {}", - commitment.num_vars, max_num_vars + commitment.num_vars(), + max_num_vars ))); } - if commitment.layout_digest != default_layout_digest { + if commitment.layout_digest() != default_layout_digest { return Err(invalid_batch( "Akita packing commitment layout digest does not match setup", )); } - if commitment.layout_digest != layout.digest { + if commitment.layout_digest() != layout.digest { return Err(invalid_batch( "Akita packing commitment layout digest does not match setup layout", )); } - if commitment.num_vars != layout.dimension { + if commitment.num_vars() != layout.dimension { return Err(invalid_batch(format!( "Akita packing commitment dimension {} does not match layout dimension {}", - commitment.num_vars, layout.dimension + commitment.num_vars(), + layout.dimension ))); } - if commitment.poly_count != 1 { + if commitment.poly_count() != 1 { return Err(invalid_batch(format!( "Akita packing witness commitment must contain one polynomial, got {}", - commitment.poly_count + commitment.poly_count() ))); } Ok(()) } -fn packed_source_sparse_polynomial( - source: &S, -) -> Result, OpeningsError> +fn packed_source_unit_indices(source: &S) -> Result>, OpeningsError> where S: PackingWitnessSource, { @@ -230,10 +214,10 @@ where layout.dimension ))); } - let domain_size = 1usize << layout.dimension; - if domain_size < AKITA_D { + if !AkitaScheme::supports_unit_sparse_dimension(layout.dimension) { return Ok(None); } + let domain_size = 1usize << layout.dimension; if layout.cells > domain_size { return Err(invalid_batch(format!( "Akita packing witness has {} cells but dimension {} supports {domain_size}", @@ -271,7 +255,7 @@ where }); result?; - AkitaSparsePolynomial::from_jolt_unit_indices(layout.dimension, ranks).map(Some) + Ok(Some(ranks)) } fn singleton_statement( @@ -282,7 +266,7 @@ fn singleton_statement( BatchOpeningStatement { logical_point: point.to_vec(), pcs_point: point.to_vec(), - layout_digest: commitment.layout_digest, + layout_digest: commitment.layout_digest(), claims: vec![jolt_openings::BatchOpeningClaim { id: (), relation: (), diff --git a/crates/jolt-verifier/src/akita_validation.rs b/crates/jolt-verifier/src/akita_validation.rs index dbcc67e4b3..c2ae7cbac8 100644 --- a/crates/jolt-verifier/src/akita_validation.rs +++ b/crates/jolt-verifier/src/akita_validation.rs @@ -103,20 +103,20 @@ pub(crate) fn validate_akita_proof_payload_shape( got: proof_commitments.family(), })?; validate_akita_verifier_setup_shape(setup, payload.layout_digest, payload.d_pack)?; - if payload.packed_witness.layout_digest != payload.layout_digest { + if payload.packed_witness.layout_digest() != payload.layout_digest { return Err(VerifierError::InvalidProtocolConfig { reason: "lattice packing witness commitment layout digest does not match proof payload" .to_string(), }); } - if payload.packed_witness.num_vars != payload.d_pack { + if payload.packed_witness.num_vars() != payload.d_pack { return Err(VerifierError::InvalidProtocolConfig { reason: "lattice packing witness commitment dimension does not match proof payload D_pack" .to_string(), }); } - if payload.packed_witness.poly_count != 1 { + if payload.packed_witness.poly_count() != 1 { return Err(VerifierError::InvalidProtocolConfig { reason: "lattice packing witness commitment must contain exactly one polynomial" .to_string(), @@ -137,24 +137,24 @@ pub(crate) fn validate_akita_opening_proof_payload_shape( expected: PcsFamily::Lattice, got: proof_commitments.family(), })?; - if opening_proof.native.commitment != payload.packed_witness { + if opening_proof.native.commitment() != &payload.packed_witness { return Err(VerifierError::InvalidProtocolConfig { reason: "lattice packed opening proof commitment does not match packed witness payload" .to_string(), }); } - validate_akita_commitment_bytes(&opening_proof.native.commitment)?; - if opening_proof.native.statement_bridge.is_empty() { + validate_akita_commitment_bytes(opening_proof.native.commitment())?; + if opening_proof.native.statement_bridge().is_empty() { return Err(VerifierError::InvalidProtocolConfig { reason: "Akita opening proof is missing statement bridge bytes".to_string(), }); } - if opening_proof.native.proof_shape.is_empty() { + if opening_proof.native.proof_shape().is_empty() { return Err(VerifierError::InvalidProtocolConfig { reason: "Akita opening proof is missing native proof shape bytes".to_string(), }); } - if opening_proof.native.proof.is_empty() { + if opening_proof.native.proof_bytes().is_empty() { return Err(VerifierError::InvalidProtocolConfig { reason: "Akita opening proof is missing native proof bytes".to_string(), }); @@ -208,7 +208,7 @@ fn validate_akita_precommitted_opening_proof_payload_shape( expected: PcsFamily::Lattice, got: proof_commitments.family(), })?; - if opening_proof.native.commitment == payload.packed_witness { + if opening_proof.native.commitment() == &payload.packed_witness { return Err(VerifierError::InvalidProtocolConfig { reason: "lattice precommitted opening proof must target a separate precommitted commitment" @@ -221,24 +221,33 @@ fn validate_akita_precommitted_opening_proof_payload_shape( .to_string(), }); } + if opening_proof.native.commitment().num_vars() != payload.d_pack { + return Err(VerifierError::InvalidProtocolConfig { + reason: format!( + "lattice precommitted opening commitment dimension {} does not match Akita setup dimension {}", + opening_proof.native.commitment().num_vars(), + payload.d_pack + ), + }); + } validate_akita_native_opening_proof_payload_shape(&opening_proof.native) } fn validate_akita_native_opening_proof_payload_shape( opening_proof: &AkitaBatchProof, ) -> Result<(), VerifierError> { - validate_akita_commitment_bytes(&opening_proof.commitment)?; - if opening_proof.statement_bridge.is_empty() { + validate_akita_commitment_bytes(opening_proof.commitment())?; + if opening_proof.statement_bridge().is_empty() { return Err(VerifierError::InvalidProtocolConfig { reason: "Akita opening proof is missing statement bridge bytes".to_string(), }); } - if opening_proof.proof_shape.is_empty() { + if opening_proof.proof_shape().is_empty() { return Err(VerifierError::InvalidProtocolConfig { reason: "Akita opening proof is missing native proof shape bytes".to_string(), }); } - if opening_proof.proof.is_empty() { + if opening_proof.proof_bytes().is_empty() { return Err(VerifierError::InvalidProtocolConfig { reason: "Akita opening proof is missing native proof bytes".to_string(), }); @@ -247,7 +256,7 @@ fn validate_akita_native_opening_proof_payload_shape( } fn validate_akita_commitment_bytes(commitment: &AkitaCommitment) -> Result<(), VerifierError> { - if commitment.native.is_empty() { + if commitment.native_bytes().is_empty() { return Err(VerifierError::InvalidProtocolConfig { reason: "Akita commitment is missing native commitment bytes".to_string(), }); @@ -375,27 +384,27 @@ fn validate_akita_verifier_setup_shape( expected_digest: [u8; 32], expected_dimension: usize, ) -> Result<(), VerifierError> { - if setup.pcs.default_layout_digest != expected_digest { + if setup.pcs.default_layout_digest() != expected_digest { return Err(VerifierError::InvalidProtocolConfig { reason: "lattice packing verifier setup layout digest does not match packed witness layout" .to_string(), }); } - if setup.pcs.max_num_vars != expected_dimension { + if setup.pcs.max_num_vars() != expected_dimension { return Err(VerifierError::InvalidProtocolConfig { reason: "lattice packing verifier setup max_num_vars does not match packed witness dimension" .to_string(), }); } - if setup.pcs.max_num_polys_per_commitment_group == 0 { + if setup.pcs.max_num_polys_per_commitment_group() == 0 { return Err(VerifierError::InvalidProtocolConfig { reason: "lattice packing verifier setup must support at least one polynomial per commitment group" .to_string(), }); } - if setup.pcs.native.is_empty() { + if setup.pcs.native_bytes().is_empty() { return Err(VerifierError::InvalidProtocolConfig { reason: "Akita verifier setup is missing native setup bytes".to_string(), }); @@ -428,6 +437,49 @@ mod tests { family.into() } + fn verifier_setup_with_parts( + setup: &AkitaPackingVerifierSetup, + max_num_polys_per_commitment_group: usize, + native: Vec, + ) -> AkitaPackingVerifierSetup { + let pcs = serde_json::from_value(serde_json::json!({ + "max_num_vars": setup.pcs.max_num_vars(), + "max_num_polys_per_commitment_group": max_num_polys_per_commitment_group, + "default_layout_digest": setup.pcs.default_layout_digest(), + "native": native, + })) + .expect("tampered verifier setup should deserialize"); + AkitaPackingVerifierSetup { + pcs, + layout: setup.layout.clone(), + } + } + + fn commitment_with_parts( + layout_digest: [u8; 32], + num_vars: usize, + poly_count: usize, + native: Vec, + ) -> AkitaCommitment { + serde_json::from_value(serde_json::json!({ + "layout_digest": layout_digest, + "num_vars": num_vars, + "poly_count": poly_count, + "native": native, + })) + .expect("tampered commitment should deserialize") + } + + fn native_opening_proof_with_commitment(commitment: AkitaCommitment) -> AkitaBatchProof { + serde_json::from_value(serde_json::json!({ + "commitment": commitment, + "statement_bridge": [1], + "proof_shape": [2], + "proof": [3], + })) + .expect("tampered native proof should deserialize") + } + fn tiny_layout() -> PackingWitnessLayout { let specs = vec![ PackingFamilySpec::direct( @@ -536,8 +588,11 @@ mod tests { Err(VerifierError::InvalidProtocolConfig { .. }) )); - let mut missing_native = verifier_setup; - missing_native.pcs.native.clear(); + let missing_native = verifier_setup_with_parts( + &verifier_setup, + verifier_setup.pcs.max_num_polys_per_commitment_group(), + Vec::new(), + ); assert!(matches!( validate_akita_verifier_setup_config(&missing_native, &config), Err(VerifierError::InvalidProtocolConfig { reason }) @@ -565,8 +620,11 @@ mod tests { Err(VerifierError::InvalidProtocolConfig { .. }) )); - let mut zero_group_setup = verifier_setup; - zero_group_setup.pcs.max_num_polys_per_commitment_group = 0; + let zero_group_setup = verifier_setup_with_parts( + &verifier_setup, + 0, + verifier_setup.pcs.native_bytes().to_vec(), + ); assert!(matches!( validate_akita_verifier_setup_layout(&zero_group_setup, &layout), Err(VerifierError::InvalidProtocolConfig { .. }) @@ -582,7 +640,15 @@ mod tests { let payload = lattice_payload(&artifacts); let mut wrong_commitment_digest = payload.clone(); - wrong_commitment_digest.packed_witness.layout_digest = [9; 32]; + wrong_commitment_digest.packed_witness = commitment_with_parts( + [9; 32], + wrong_commitment_digest.packed_witness.num_vars(), + wrong_commitment_digest.packed_witness.poly_count(), + wrong_commitment_digest + .packed_witness + .native_bytes() + .to_vec(), + ); assert!(matches!( validate_akita_proof_payload_shape( &verifier_setup, @@ -593,7 +659,15 @@ mod tests { )); let mut wrong_commitment_dimension = payload.clone(); - wrong_commitment_dimension.packed_witness.num_vars = layout.dimension + 1; + wrong_commitment_dimension.packed_witness = commitment_with_parts( + wrong_commitment_dimension.packed_witness.layout_digest(), + layout.dimension + 1, + wrong_commitment_dimension.packed_witness.poly_count(), + wrong_commitment_dimension + .packed_witness + .native_bytes() + .to_vec(), + ); assert!(matches!( validate_akita_proof_payload_shape( &verifier_setup, @@ -604,7 +678,12 @@ mod tests { )); let mut wrong_poly_count = payload.clone(); - wrong_poly_count.packed_witness.poly_count = 2; + wrong_poly_count.packed_witness = commitment_with_parts( + wrong_poly_count.packed_witness.layout_digest(), + wrong_poly_count.packed_witness.num_vars(), + 2, + wrong_poly_count.packed_witness.native_bytes().to_vec(), + ); assert!(matches!( validate_akita_proof_payload_shape( &verifier_setup, @@ -615,7 +694,12 @@ mod tests { )); let mut missing_native_commitment = payload; - missing_native_commitment.packed_witness.native.clear(); + missing_native_commitment.packed_witness = commitment_with_parts( + missing_native_commitment.packed_witness.layout_digest(), + missing_native_commitment.packed_witness.num_vars(), + missing_native_commitment.packed_witness.poly_count(), + Vec::new(), + ); assert!(matches!( validate_akita_proof_payload_shape( &verifier_setup, @@ -650,7 +734,14 @@ mod tests { )); let mut other_commitment = packed_witness.clone(); - other_commitment.layout_digest[0] ^= 1; + let mut other_digest = other_commitment.layout_digest(); + other_digest[0] ^= 1; + other_commitment = commitment_with_parts( + other_digest, + other_commitment.num_vars(), + other_commitment.poly_count(), + other_commitment.native_bytes().to_vec(), + ); assert!(matches!( validate_akita_advice_commitment_aliases( &artifacts.commitments, @@ -685,7 +776,14 @@ mod tests { )); let mut separate_commitment = packed_witness.clone(); - separate_commitment.layout_digest[0] ^= 1; + let mut separate_digest = separate_commitment.layout_digest(); + separate_digest[0] ^= 1; + separate_commitment = commitment_with_parts( + separate_digest, + separate_commitment.num_vars(), + separate_commitment.poly_count(), + separate_commitment.native_bytes().to_vec(), + ); validate_akita_precommitted_commitment_is_separate( packed_witness, &separate_commitment, @@ -694,6 +792,31 @@ mod tests { .expect("separate precommitted commitment should pass"); } + #[test] + fn akita_precommitted_opening_proof_requires_setup_dimension() { + let (_, artifacts) = empty_artifacts(tiny_layout()); + let payload = lattice_payload(&artifacts); + let precommitted = commitment_with_parts( + [31; 32], + payload.d_pack + 1, + 1, + payload.packed_witness.native_bytes().to_vec(), + ); + let proof = AkitaPackingBatchProof { + reduction: None, + native: native_opening_proof_with_commitment(precommitted), + }; + + assert!(matches!( + validate_akita_precommitted_opening_proof_payload_shapes( + &artifacts.commitments, + std::slice::from_ref(&proof), + ), + Err(VerifierError::InvalidProtocolConfig { reason }) + if reason.contains("does not match Akita setup dimension") + )); + } + #[test] fn akita_artifact_preflight_rejects_stale_protocol_and_commitments() { let layout = tiny_layout(); diff --git a/crates/jolt-verifier/src/proof.rs b/crates/jolt-verifier/src/proof.rs index 34242a36f3..3301f54fea 100644 --- a/crates/jolt-verifier/src/proof.rs +++ b/crates/jolt-verifier/src/proof.rs @@ -148,6 +148,38 @@ where trace_polynomial_order: TracePolynomialOrder, ) -> Self { let protocol = JoltProtocolConfig::for_zk(claims.is_zk()); + Self::new_with_protocol( + protocol, + commitments, + stages, + joint_opening_proof, + untrusted_advice_commitment, + claims, + trace_length, + ram_k, + rw_config, + one_hot_config, + trace_polynomial_order, + ) + } + + #[expect( + clippy::too_many_arguments, + reason = "Constructor mirrors the proof payload while keeping internal verifier claims private." + )] + pub fn new_with_protocol( + protocol: JoltProtocolConfig, + commitments: CommitmentPayload, + stages: JoltStageProofs, + joint_opening_proof: PCS::Proof, + untrusted_advice_commitment: Option, + claims: JoltProofClaims, + trace_length: usize, + ram_k: usize, + rw_config: JoltReadWriteConfig, + one_hot_config: JoltOneHotConfig, + trace_polynomial_order: TracePolynomialOrder, + ) -> Self { Self { protocol, commitments, diff --git a/crates/jolt-verifier/src/stages/stage8/precommitted.rs b/crates/jolt-verifier/src/stages/stage8/precommitted.rs index b163eadebb..a876c47b60 100644 --- a/crates/jolt-verifier/src/stages/stage8/precommitted.rs +++ b/crates/jolt-verifier/src/stages/stage8/precommitted.rs @@ -1,14 +1,14 @@ use super::{ outputs::{Stage8OpeningStatement, Stage8PhysicalOpening}, - verify::{clear_batch_claims, Stage8BatchEntry}, + verify::Stage8BatchEntry, }; use crate::VerifierError; use jolt_field::Field; use jolt_openings::{ - BatchOpeningScheme, BatchOpeningStatement, CommitmentLayoutDigest, PhysicalView, - VerifierOpeningClaim, + BatchOpeningClaim, BatchOpeningScheme, BatchOpeningStatement, CommitmentLayoutDigest, + EvaluationClaim, PhysicalView, VerifierOpeningClaim, }; -use jolt_poly::{Point, HIGH_TO_LOW}; +use jolt_poly::Point; use jolt_transcript::Transcript; type Stage8PrecommittedStatementBuild = ( @@ -51,8 +51,6 @@ where pub(super) fn precommitted_clear_statements( entries: &[Stage8BatchEntry<'_, F, C>], default_layout_digest: [u8; 32], - point: &[F], - pcs_opening_point: &Point, ) -> Result, VerifierError> where F: Field, @@ -66,17 +64,29 @@ where relation: entry.id, view: PhysicalView::Direct, }; - let (mut entry_claims, claims) = clear_batch_claims( - std::slice::from_ref(entry), - std::slice::from_ref(&physical), - pcs_opening_point, - )?; - opening_claims.append(&mut entry_claims); + let opening_claim = + entry + .opening_claim + .ok_or_else(|| VerifierError::FinalOpeningBatchFailed { + reason: "missing clear opening claim in final batch".to_string(), + })?; + let own_point = Point::high_to_low(entry.own_point.clone()); + opening_claims.push(VerifierOpeningClaim { + commitment: entry.commitment.clone(), + evaluation: EvaluationClaim::new(own_point, opening_claim * entry.scale), + }); statements.push(BatchOpeningStatement { - logical_point: point.to_vec(), - pcs_point: point.to_vec(), + logical_point: entry.own_point.clone(), + pcs_point: entry.own_point.clone(), layout_digest: direct_statement_layout_digest(entry.commitment, default_layout_digest), - claims, + claims: vec![BatchOpeningClaim { + id: entry.id, + relation: physical.relation, + commitment: entry.commitment.clone(), + claim: opening_claim, + view: physical.view, + scale: entry.scale, + }], }); } @@ -106,6 +116,8 @@ mod tests { use jolt_claims::protocols::jolt::{JoltOpeningId, JoltRelationId}; use jolt_field::{Fr, FromPrimitiveInt}; use jolt_openings::{BatchOpeningClaim, BatchOpeningResult, CommitmentScheme, OpeningsError}; + #[cfg(feature = "akita")] + use jolt_poly::Point; use jolt_poly::{MultilinearPoly, Polynomial}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -287,12 +299,13 @@ mod tests { fn precommitted_statements_use_lattice_commitment_layout_digest() { let digest = [23; 32]; let default_digest = [17; 32]; - let commitment = jolt_akita::AkitaCommitment { - layout_digest: digest, - num_vars: 1, - poly_count: 1, - native: vec![1], - }; + let commitment: jolt_akita::AkitaCommitment = serde_json::from_value(serde_json::json!({ + "layout_digest": digest, + "num_vars": 1, + "poly_count": 1, + "native": [1], + })) + .expect("test Akita commitment should deserialize"); let id = Stage8OpeningId::from(JoltOpeningId::committed( JoltCommittedPolynomial::TrustedAdvice, JoltRelationId::AdviceClaimReduction, @@ -301,17 +314,24 @@ mod tests { id, commitment: &commitment, opening_claim: Some(Fr::from_u64(7)), - own_point: vec![Fr::from_u64(0)], - scale: Fr::from_u64(1), + own_point: vec![Fr::from_u64(2)], + scale: Fr::from_u64(3), }; - let point = vec![Fr::from_u64(0)]; - let pcs_opening_point = Point::high_to_low(point.clone()); - let (_, statements) = - precommitted_clear_statements(&[entry], default_digest, &point, &pcs_opening_point) - .expect("precommitted statement should build"); + let (opening_claims, statements) = precommitted_clear_statements(&[entry], default_digest) + .expect("precommitted statement should build"); + assert_eq!(opening_claims.len(), 1); + assert_eq!( + opening_claims[0].evaluation.point, + Point::high_to_low(vec![Fr::from_u64(2)]) + ); + assert_eq!(opening_claims[0].evaluation.value, Fr::from_u64(21)); assert_eq!(statements.len(), 1); + assert_eq!(statements[0].logical_point, vec![Fr::from_u64(2)]); + assert_eq!(statements[0].pcs_point, vec![Fr::from_u64(2)]); assert_eq!(statements[0].layout_digest, digest); assert_ne!(statements[0].layout_digest, default_digest); + assert_eq!(statements[0].claims[0].claim, Fr::from_u64(7)); + assert_eq!(statements[0].claims[0].scale, Fr::from_u64(3)); } } diff --git a/crates/jolt-verifier/src/stages/stage8/verify.rs b/crates/jolt-verifier/src/stages/stage8/verify.rs index fffbbae39e..2f69ab562f 100644 --- a/crates/jolt-verifier/src/stages/stage8/verify.rs +++ b/crates/jolt-verifier/src/stages/stage8/verify.rs @@ -197,6 +197,12 @@ where }); } + let precommitted_coefficients = verify_precommitted_opening_batches::( + setup, + transcript, + &batch.precommitted_statements, + precommitted_proofs, + )?; let batch_result = PCS::verify_batch(setup, transcript, &batch.statement, proof).map_err(|error| { VerifierError::FinalOpeningVerificationFailed { @@ -204,12 +210,7 @@ where } })?; let mut coefficients = batch_result.coefficients; - coefficients.extend(verify_precommitted_opening_batches::( - setup, - transcript, - &batch.precommitted_statements, - precommitted_proofs, - )?); + coefficients.extend(precommitted_coefficients); Ok(Stage8ClearOutput { opening_claims: batch.opening_claims, @@ -489,12 +490,8 @@ where &physical_manifest.openings[..entries.len()], &pcs_opening_point, )?; - let (precommitted_opening_claims, precommitted_statements) = precommitted_clear_statements( - &precommitted_entries, - stage8_layout_digest(preprocessing), - &point, - &pcs_opening_point, - )?; + let (precommitted_opening_claims, precommitted_statements) = + precommitted_clear_statements(&precommitted_entries, stage8_layout_digest(preprocessing))?; opening_claims.extend(precommitted_opening_claims); Ok(Stage8BatchStatement::Clear(Stage8ClearBatchStatement { logical_manifest, diff --git a/crates/jolt-verifier/src/verifier.rs b/crates/jolt-verifier/src/verifier.rs index 944377dc24..d35e457fe3 100644 --- a/crates/jolt-verifier/src/verifier.rs +++ b/crates/jolt-verifier/src/verifier.rs @@ -2685,19 +2685,27 @@ mod tests { layout_digest: [u8; 32], d_pack: usize, ) -> TestProof { - let mut proof = proof_with_zk(false, clear_claims()); - proof.protocol = *config; - proof.trace_length = 4; - proof.ram_K = 4; - proof.one_hot_config = JoltOneHotConfig { - log_k_chunk: 8, - lookups_ra_virtual_log_k_chunk: 8, - }; - proof.commitments = CommitmentPayload::Lattice(LatticeCommitmentPayload::new( - TestCommitment, - layout_digest, - d_pack, - )); + let base = proof_with_zk(false, clear_claims()); + let mut proof = JoltProof::new_with_protocol( + *config, + CommitmentPayload::Lattice(LatticeCommitmentPayload::new( + TestCommitment, + layout_digest, + d_pack, + )), + base.stages, + (), + base.untrusted_advice_commitment, + base.claims, + 4, + 4, + base.rw_config, + JoltOneHotConfig { + log_k_chunk: 8, + lookups_ra_virtual_log_k_chunk: 8, + }, + base.trace_polynomial_order, + ); #[cfg(feature = "akita")] attach_lattice_validity_surface(&mut proof, config); proof diff --git a/crates/jolt-verifier/tests/akita_prover_support.rs b/crates/jolt-verifier/tests/akita_prover_support.rs index d0ba08cbd8..ff9ab19475 100644 --- a/crates/jolt-verifier/tests/akita_prover_support.rs +++ b/crates/jolt-verifier/tests/akita_prover_support.rs @@ -203,8 +203,8 @@ fn commits_packed_witness_and_returns_verifier_payload() { .expect("artifact should carry lattice payload"); assert_eq!(payload.layout_digest, layout.digest); assert_eq!(payload.d_pack, layout.dimension); - assert_eq!(payload.packed_witness.layout_digest, layout.digest); - assert_eq!(payload.packed_witness.num_vars, layout.dimension); + assert_eq!(payload.packed_witness.layout_digest(), layout.digest); + assert_eq!(payload.packed_witness.num_vars(), layout.dimension); assert_eq!( artifact.protocol.lattice.packed_witness.layout_digest, Some(layout.digest) From 8b883f816bf8c0cee2689da22abd0e6c7f9fc575 Mon Sep 17 00:00:00 2001 From: sumchecker <241190306+sumchecker@users.noreply.github.com> Date: Thu, 25 Jun 2026 16:58:42 -0700 Subject: [PATCH 3/7] verifier: make Stage 8 opening routing explicit Assign the packed/precommitted route when final opening entries are built so lattice partitioning no longer depends on a second ID predicate staying in sync. Co-authored-by: Cursor --- .../src/stages/stage8/precommitted.rs | 3 + .../jolt-verifier/src/stages/stage8/verify.rs | 71 ++++++++++++------- 2 files changed, 47 insertions(+), 27 deletions(-) diff --git a/crates/jolt-verifier/src/stages/stage8/precommitted.rs b/crates/jolt-verifier/src/stages/stage8/precommitted.rs index a876c47b60..a4d85e5846 100644 --- a/crates/jolt-verifier/src/stages/stage8/precommitted.rs +++ b/crates/jolt-verifier/src/stages/stage8/precommitted.rs @@ -1,3 +1,5 @@ +#[cfg(test)] +use super::verify::Stage8OpeningRoute; use super::{ outputs::{Stage8OpeningStatement, Stage8PhysicalOpening}, verify::Stage8BatchEntry, @@ -313,6 +315,7 @@ mod tests { let entry = Stage8BatchEntry { id, commitment: &commitment, + route: Stage8OpeningRoute::Precommitted, opening_claim: Some(Fr::from_u64(7)), own_point: vec![Fr::from_u64(2)], scale: Fr::from_u64(3), diff --git a/crates/jolt-verifier/src/stages/stage8/verify.rs b/crates/jolt-verifier/src/stages/stage8/verify.rs index 2f69ab562f..dce42f9ed2 100644 --- a/crates/jolt-verifier/src/stages/stage8/verify.rs +++ b/crates/jolt-verifier/src/stages/stage8/verify.rs @@ -29,7 +29,7 @@ use jolt_claims::protocols::jolt::{ lattice as lattice_formulas, ra::JoltRaPolynomialLayout, }, - JoltCommittedPolynomial, JoltOpeningId, JoltPolynomialId, JoltRelationId, + JoltCommittedPolynomial, }; use jolt_crypto::VectorCommitment; use jolt_field::Field; @@ -44,6 +44,7 @@ use jolt_transcript::Transcript; pub(super) struct Stage8BatchEntry<'a, F: Field, C> { pub(super) id: Stage8OpeningId, pub(super) commitment: &'a C, + pub(super) route: Stage8OpeningRoute, /// `None` in ZK mode, where opening claims stay committed. pub(super) opening_claim: Option, /// Point where this logical opening was produced before Stage 8 embedding. @@ -53,6 +54,33 @@ pub(super) struct Stage8BatchEntry<'a, F: Field, C> { pub(super) scale: F, } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(super) enum Stage8OpeningRoute { + MainBatch, + Precommitted, +} + +struct Stage8EntryTarget<'a, C> { + commitment: &'a C, + route: Stage8OpeningRoute, +} + +impl<'a, C> Stage8EntryTarget<'a, C> { + const fn main_batch(commitment: &'a C) -> Self { + Self { + commitment, + route: Stage8OpeningRoute::MainBatch, + } + } + + const fn precommitted(commitment: &'a C) -> Self { + Self { + commitment, + route: Stage8OpeningRoute::Precommitted, + } + } +} + struct LatticeUnsignedIncFinalOpenings<'a, F: Field> { chunk_point: &'a [F], chunk_claims: Option<&'a [F]>, @@ -375,6 +403,7 @@ where trusted_advice_commitment, polynomial, ) + .map(Stage8EntryTarget::main_batch) }, #[cfg(feature = "field-inline")] &commitments.field_inline.field_registers.rd_inc, @@ -401,8 +430,9 @@ where trusted_advice_commitment, polynomial, ) + .map(Stage8EntryTarget::precommitted) } else { - Ok(&payload.packed_witness) + Ok(Stage8EntryTarget::main_batch(&payload.packed_witness)) } }, #[cfg(feature = "field-inline")] @@ -416,7 +446,7 @@ where )?); let (entries, precommitted_entries): (Vec<_>, Vec<_>) = final_entries .into_iter() - .partition(|entry| !lattice_precommitted_stage8_opening(entry.id)); + .partition(|entry| entry.route == Stage8OpeningRoute::MainBatch); (entries, precommitted_entries) } }; @@ -564,7 +594,7 @@ where clippy::too_many_arguments, reason = "gathers per-polynomial sources from several stages" )] -fn batch_entries<'a, F, C, CommitmentFor>( +fn batch_entries<'a, F, C, TargetFor>( layout: JoltRaPolynomialLayout, committed_bytecode_chunk_count: Option, include_trusted_advice: bool, @@ -575,12 +605,12 @@ fn batch_entries<'a, F, C, CommitmentFor>( precommitted_finals: &'a [PrecommittedFinalOpening], clear_claims: Option<(&Stage6Claims, &Stage7Claims)>, skip_increment_openings: bool, - mut commitment_for: CommitmentFor, + mut target_for: TargetFor, #[cfg(feature = "field-inline")] field_rd_inc_commitment: &'a C, ) -> Result>, VerifierError> where F: Field, - CommitmentFor: FnMut(JoltCommittedPolynomial) -> Result<&'a C, VerifierError>, + TargetFor: FnMut(JoltCommittedPolynomial) -> Result, VerifierError>, { let precommitted_final = |polynomial: JoltCommittedPolynomial| { precommitted_finals @@ -683,10 +713,11 @@ where (opening.point.as_slice(), opening.opening_claim) } }; - let commitment = commitment_for(polynomial)?; + let target = target_for(polynomial)?; entries.push(Stage8BatchEntry { id: id.into(), - commitment, + commitment: target.commitment, + route: target.route, opening_claim, own_point: own_point.to_vec(), scale: commitment_embedding_scale(opening_point, own_point), @@ -697,6 +728,7 @@ where entries.push(Stage8BatchEntry { id: field_increments::field_rd_inc_reduced_opening().into(), commitment: field_rd_inc_commitment, + route: Stage8OpeningRoute::MainBatch, opening_claim: clear_claims.map(|(stage6, _)| { stage6 .field_inline @@ -747,6 +779,7 @@ where entries.push(Stage8BatchEntry { id: Stage8OpeningId::from(lattice_formulas::unsigned_inc_chunk_opening(index)), commitment, + route: Stage8OpeningRoute::MainBatch, opening_claim: sources.chunk_claims.map(|claims| claims[index]), own_point: sources.chunk_point.to_vec(), scale: commitment_embedding_scale(opening_point, sources.chunk_point), @@ -755,6 +788,7 @@ where entries.push(Stage8BatchEntry { id: Stage8OpeningId::from(lattice_formulas::unsigned_inc_msb_opening()), commitment, + route: Stage8OpeningRoute::MainBatch, opening_claim: sources.msb_claim, own_point: sources.msb_point.to_vec(), scale: commitment_embedding_scale(opening_point, sources.msb_point), @@ -843,23 +877,6 @@ fn lattice_requires_precommitted_opening(polynomial: JoltCommittedPolynomial) -> ) } -fn lattice_precommitted_stage8_opening(id: Stage8OpeningId) -> bool { - matches!( - id, - Stage8OpeningId::Jolt( - JoltOpeningId::TrustedAdvice { - relation: JoltRelationId::AdviceClaimReduction, - } | JoltOpeningId::Polynomial { - polynomial: JoltPolynomialId::Committed( - JoltCommittedPolynomial::BytecodeChunk(_) - | JoltCommittedPolynomial::ProgramImageInit, - ), - .. - }, - ) - ) -} - #[cfg(feature = "akita")] fn lattice_stage8_physical_manifest( config: &crate::config::JoltProtocolConfig, @@ -1231,7 +1248,7 @@ mod tests { &program_image_only, None, true, - |_| Ok(&commitment), + |_| Ok(Stage8EntryTarget::main_batch(&commitment)), #[cfg(feature = "field-inline")] &commitment, ) @@ -1259,7 +1276,7 @@ mod tests { &bytecode_only, None, true, - |_| Ok(&commitment), + |_| Ok(Stage8EntryTarget::main_batch(&commitment)), #[cfg(feature = "field-inline")] &commitment, ) From 88d5a97bc5337170e31692c2376a6379ea67af2c Mon Sep 17 00:00:00 2001 From: sumchecker <241190306+sumchecker@users.noreply.github.com> Date: Thu, 25 Jun 2026 17:51:19 -0700 Subject: [PATCH 4/7] openings: split direct and packed batch proofs Represent packing batch proofs as explicit direct or packed variants so Akita precommitted openings no longer masquerade as packed proofs without reductions. Co-authored-by: Cursor --- crates/jolt-openings/src/packing.rs | 2 +- crates/jolt-openings/src/packing/batch.rs | 77 +++++-------- crates/jolt-openings/src/packing/types.rs | 36 +++++- crates/jolt-verifier/src/akita_openings.rs | 103 ++++++++++-------- crates/jolt-verifier/src/akita_packing.rs | 10 +- crates/jolt-verifier/src/akita_validation.rs | 46 ++++---- .../src/stages/stage8/precommitted.rs | 14 +-- .../jolt-verifier/src/stages/stage8/verify.rs | 10 -- 8 files changed, 143 insertions(+), 155 deletions(-) diff --git a/crates/jolt-openings/src/packing.rs b/crates/jolt-openings/src/packing.rs index a3da5bc0a3..bf6e336ff4 100644 --- a/crates/jolt-openings/src/packing.rs +++ b/crates/jolt-openings/src/packing.rs @@ -322,7 +322,7 @@ mod tests { vec![hint], ) .expect("generic packed proof should prove"); - assert!(proof.reduction.is_some()); + assert!(matches!(proof, PackingBatchProof::Packed { .. })); let mut verifier_transcript = Blake2bTranscript::new(b"generic-packing"); let result = ::verify_batch( diff --git a/crates/jolt-openings/src/packing/batch.rs b/crates/jolt-openings/src/packing/batch.rs index 27bb40f222..48de10b042 100644 --- a/crates/jolt-openings/src/packing/batch.rs +++ b/crates/jolt-openings/src/packing/batch.rs @@ -77,10 +77,7 @@ where hint: Option, transcript: &mut impl Transcript, ) -> Self::Proof { - PackingBatchProof { - reduction: None, - native: PCS::open(poly, point, eval, &setup.pcs, hint, transcript), - } + PackingBatchProof::direct(PCS::open(poly, point, eval, &setup.pcs, hint, transcript)) } fn verify( @@ -91,17 +88,10 @@ where setup: &Self::VerifierSetup, transcript: &mut impl Transcript, ) -> Result<(), OpeningsError> { - if proof.reduction.is_some() { + let PackingBatchProof::Direct { native } = proof else { return Err(OpeningsError::VerificationFailed); - } - PCS::verify( - commitment, - point, - eval, - &proof.native, - &setup.pcs, - transcript, - ) + }; + PCS::verify(commitment, point, eval, native, &setup.pcs, transcript) } fn bind_opening_inputs( @@ -131,10 +121,7 @@ where { if !has_packing_view(statement) { let native = PCS::prove_batch(&setup.pcs, transcript, statement, polynomials, hints)?; - return Ok(PackingBatchProof { - reduction: None, - native, - }); + return Ok(PackingBatchProof::direct(native)); } let layout = &setup.layout; @@ -159,10 +146,7 @@ where Some(hint), transcript, ); - Ok(PackingBatchProof { - reduction: Some(reduction.proof), - native, - }) + Ok(PackingBatchProof::packed(reduction.proof, native)) } fn verify_batch( @@ -175,16 +159,19 @@ where T: Transcript, { if !has_packing_view(statement) { - if proof.reduction.is_some() { + let PackingBatchProof::Direct { native } = proof else { return Err(OpeningsError::VerificationFailed); - } - return PCS::verify_batch(&setup.pcs, transcript, statement, &proof.native); + }; + return PCS::verify_batch(&setup.pcs, transcript, statement, native); } - let reduction_proof = proof - .reduction - .as_ref() - .ok_or(OpeningsError::VerificationFailed)?; + let PackingBatchProof::Packed { + reduction: reduction_proof, + native, + } = proof + else { + return Err(OpeningsError::VerificationFailed); + }; let layout = &setup.layout; let commitment = validate_packing_statement(layout, statement)?; validate_packed_commitment_digest(layout, &commitment)?; @@ -193,7 +180,7 @@ where &reduction.result.joint_commitment, &reduction.opening_point, reduction.opening_eval, - &proof.native, + native, &setup.pcs, transcript, )?; @@ -225,14 +212,7 @@ where transcript: &mut impl Transcript, ) -> (Self::Proof, Self::HidingCommitment, Self::Blind) { let (native, hiding, blind) = PCS::open_zk(poly, point, eval, &setup.pcs, hint, transcript); - ( - PackingBatchProof { - reduction: None, - native, - }, - hiding, - blind, - ) + (PackingBatchProof::direct(native), hiding, blind) } fn verify_zk( @@ -242,10 +222,10 @@ where setup: &Self::VerifierSetup, transcript: &mut impl Transcript, ) -> Result { - if proof.reduction.is_some() { + let PackingBatchProof::Direct { native } = proof else { return Err(OpeningsError::VerificationFailed); - } - PCS::verify_zk(commitment, point, &proof.native, &setup.pcs, transcript) + }; + PCS::verify_zk(commitment, point, native, &setup.pcs, transcript) } fn bind_zk_opening_inputs( @@ -281,14 +261,7 @@ where } let (native, hiding, blind) = PCS::prove_batch_zk(&setup.pcs, transcript, statement, evals, polynomials, hints)?; - Ok(( - PackingBatchProof { - reduction: None, - native, - }, - hiding, - blind, - )) + Ok((PackingBatchProof::direct(native), hiding, blind)) } fn verify_batch_zk( @@ -305,10 +278,10 @@ where "packing batch openings do not support ZK mode yet", )); } - if proof.reduction.is_some() { + let PackingBatchProof::Direct { native } = proof else { return Err(OpeningsError::VerificationFailed); - } - PCS::verify_batch_zk(&setup.pcs, transcript, statement, &proof.native) + }; + PCS::verify_batch_zk(&setup.pcs, transcript, statement, native) } } diff --git a/crates/jolt-openings/src/packing/types.rs b/crates/jolt-openings/src/packing/types.rs index 1f1ac3cab8..d581b5d9fc 100644 --- a/crates/jolt-openings/src/packing/types.rs +++ b/crates/jolt-openings/src/packing/types.rs @@ -69,10 +69,38 @@ pub struct PackingReductionProof { } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct PackingBatchProof { - pub reduction: Option, - pub native: NativeProof, +#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)] +pub enum PackingBatchProof { + Direct { + native: NativeProof, + }, + Packed { + reduction: PackingReductionProof, + native: NativeProof, + }, +} + +impl PackingBatchProof { + pub fn direct(native: NativeProof) -> Self { + Self::Direct { native } + } + + pub fn packed(reduction: PackingReductionProof, native: NativeProof) -> Self { + Self::Packed { reduction, native } + } + + pub fn native(&self) -> &NativeProof { + match self { + Self::Direct { native } | Self::Packed { native, .. } => native, + } + } + + pub fn reduction(&self) -> Option<&PackingReductionProof> { + match self { + Self::Direct { .. } => None, + Self::Packed { reduction, .. } => Some(reduction), + } + } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] diff --git a/crates/jolt-verifier/src/akita_openings.rs b/crates/jolt-verifier/src/akita_openings.rs index 8637e2ba33..77efafcfdd 100644 --- a/crates/jolt-verifier/src/akita_openings.rs +++ b/crates/jolt-verifier/src/akita_openings.rs @@ -124,11 +124,6 @@ where .ok_or_else(|| VerifierError::FinalOpeningBatchFailed { reason: "lattice packing opening artifacts do not carry a lattice payload".to_string(), })?; - validate_akita_precommitted_opening_inputs( - &payload.packed_witness, - &statement.precommitted_statements, - precommitted_inputs, - )?; let precommitted = prove_akita_precommitted_opening_batches( setup, transcript, @@ -419,7 +414,7 @@ mod tests { use crate::{ akita::commit_akita_packing_witness, akita_validation::{ - validate_akita_opening_proof_payload_shape, + validate_akita_packed_target_opening_proof_payload_shape, validate_akita_packing_opening_proof_payload_shape, validate_akita_precommitted_opening_proof_payload_shapes, }, @@ -517,6 +512,17 @@ mod tests { serde_json::from_value(value).expect("tampered proof should deserialize") } + fn packing_proof_with_native( + proof: &AkitaPackingBatchProof, + native: AkitaBatchProof, + ) -> AkitaPackingBatchProof { + if let Some(reduction) = proof.reduction() { + AkitaPackingBatchProof::packed(reduction.clone(), native) + } else { + AkitaPackingBatchProof::direct(native) + } + } + fn akita_packing_params( layout: &jolt_openings::PackingWitnessLayout, max_num_polys_per_commitment_group: usize, @@ -626,7 +632,7 @@ mod tests { &stage8_statement, ) .expect("packed batch proof should be produced"); - validate_akita_opening_proof_payload_shape(&artifact.commitments, &proof) + validate_akita_packed_target_opening_proof_payload_shape(&artifact.commitments, &proof) .expect("fresh packed batch proof shape should pass preflight"); let mut wrong_stage8_statement = stage8_statement.clone(); @@ -650,13 +656,19 @@ mod tests { )); let mut wrong_commitment_proof = proof.clone(); - wrong_commitment_proof.native = proof_with_parts( - &wrong_commitment_proof.native, - commitment_with_layout_digest(wrong_commitment_proof.native.commitment(), [9; 32]), - wrong_commitment_proof.native.proof_bytes().to_vec(), + wrong_commitment_proof = packing_proof_with_native( + &wrong_commitment_proof, + proof_with_parts( + wrong_commitment_proof.native(), + commitment_with_layout_digest( + wrong_commitment_proof.native().commitment(), + [9; 32], + ), + wrong_commitment_proof.native().proof_bytes().to_vec(), + ), ); assert!(matches!( - validate_akita_opening_proof_payload_shape( + validate_akita_packed_target_opening_proof_payload_shape( &artifact.commitments, &wrong_commitment_proof, ), @@ -665,19 +677,21 @@ mod tests { )); let mut missing_native_proof = proof.clone(); - missing_native_proof.native = proof_with_parts( - &missing_native_proof.native, - missing_native_proof.native.commitment().clone(), - Vec::new(), + missing_native_proof = packing_proof_with_native( + &missing_native_proof, + proof_with_parts( + missing_native_proof.native(), + missing_native_proof.native().commitment().clone(), + Vec::new(), + ), ); assert!(matches!( - validate_akita_opening_proof_payload_shape(&artifact.commitments, &missing_native_proof), + validate_akita_packed_target_opening_proof_payload_shape(&artifact.commitments, &missing_native_proof), Err(VerifierError::InvalidProtocolConfig { reason }) if reason.contains("native proof bytes") )); - let mut missing_reduction = proof.clone(); - missing_reduction.reduction = None; + let missing_reduction = AkitaPackingBatchProof::direct(proof.native().clone()); assert!(matches!( validate_akita_packing_opening_proof_payload_shape( &artifact.commitments, @@ -688,15 +702,15 @@ mod tests { if reason.contains("packed reduction") )); - let mut missing_reduction_eval = proof.clone(); - missing_reduction_eval - .reduction - .as_mut() + let mut reduction = proof + .reduction() .expect("packed proof should contain a reduction") - .opening_eval - .clear(); + .clone(); + reduction.opening_eval.clear(); + let missing_reduction_eval = + AkitaPackingBatchProof::packed(reduction, proof.native().clone()); assert!(matches!( - validate_akita_opening_proof_payload_shape( + validate_akita_packed_target_opening_proof_payload_shape( &artifact.commitments, &missing_reduction_eval, ), @@ -704,12 +718,13 @@ mod tests { if reason.contains("lattice packing reduction opening eval") )); - let mut noncanonical_reduction_eval = proof.clone(); - noncanonical_reduction_eval - .reduction - .as_mut() + let mut reduction = proof + .reduction() .expect("packed proof should contain a reduction") - .opening_eval = AKITA_FIELD_MODULUS.to_le_bytes().to_vec(); + .clone(); + reduction.opening_eval = AKITA_FIELD_MODULUS.to_le_bytes().to_vec(); + let noncanonical_reduction_eval = + AkitaPackingBatchProof::packed(reduction, proof.native().clone()); assert!(matches!( validate_akita_packing_opening_proof_payload_shape( &artifact.commitments, @@ -849,15 +864,11 @@ mod tests { ) .expect("fresh precommitted proof payload should pass preflight"); - let mut packed_target_precommitted_proof = proofs.precommitted[0].clone(); - packed_target_precommitted_proof.native = proof_with_parts( - &packed_target_precommitted_proof.native, + let packed_target_precommitted_proof = AkitaPackingBatchProof::direct(proof_with_parts( + proofs.precommitted[0].native(), packed_commitment.clone(), - packed_target_precommitted_proof - .native - .proof_bytes() - .to_vec(), - ); + proofs.precommitted[0].native().proof_bytes().to_vec(), + )); assert!(matches!( validate_akita_precommitted_opening_proof_payload_shapes( &artifact.commitments, @@ -867,18 +878,20 @@ mod tests { if reason.contains("precommitted commitment") )); - let mut packed_reduction_precommitted_proof = proofs.precommitted[0].clone(); - packed_reduction_precommitted_proof.reduction = Some(PackingReductionProof { - rounds: Vec::new(), - opening_eval: vec![0; AkitaField::NUM_BYTES], - }); + let packed_reduction_precommitted_proof = AkitaPackingBatchProof::packed( + PackingReductionProof { + rounds: Vec::new(), + opening_eval: vec![0; AkitaField::NUM_BYTES], + }, + proofs.precommitted[0].native().clone(), + ); assert!(matches!( validate_akita_precommitted_opening_proof_payload_shapes( &artifact.commitments, std::slice::from_ref(&packed_reduction_precommitted_proof), ), Err(VerifierError::InvalidProtocolConfig { reason }) - if reason.contains("packed reduction") + if reason.contains("direct native proof") )); let mut packed_target_statement = stage8_statement.clone(); diff --git a/crates/jolt-verifier/src/akita_packing.rs b/crates/jolt-verifier/src/akita_packing.rs index ecaa5032c7..01723b893b 100644 --- a/crates/jolt-verifier/src/akita_packing.rs +++ b/crates/jolt-verifier/src/akita_packing.rs @@ -58,10 +58,7 @@ where unit_indices, hint, )?; - return Ok(PackingBatchProof { - reduction: None, - native, - }); + return Ok(PackingBatchProof::direct(native)); } let shape = validate_packed_source_prover_inputs(setup, statement, source, &hint)?; @@ -81,10 +78,7 @@ where unit_indices, hint, )?; - return Ok(PackingBatchProof { - reduction: Some(reduction.proof), - native, - }); + return Ok(PackingBatchProof::packed(reduction.proof, native)); } let polynomial = packing_witness_source_polynomial(source)?; diff --git a/crates/jolt-verifier/src/akita_validation.rs b/crates/jolt-verifier/src/akita_validation.rs index c2ae7cbac8..85307645ef 100644 --- a/crates/jolt-verifier/src/akita_validation.rs +++ b/crates/jolt-verifier/src/akita_validation.rs @@ -10,7 +10,7 @@ use crate::{ }; use jolt_akita::{AkitaBatchProof, AkitaCommitment, AkitaField, AKITA_FIELD_MODULUS}; use jolt_field::FixedByteSize; -use jolt_openings::PackingWitnessLayout; +use jolt_openings::{PackingBatchProof, PackingWitnessLayout}; pub(crate) fn validate_akita_artifacts_for_proof( setup: &AkitaPackingVerifierSetup, @@ -126,7 +126,7 @@ pub(crate) fn validate_akita_proof_payload_shape( Ok(()) } -pub(crate) fn validate_akita_opening_proof_payload_shape( +pub(crate) fn validate_akita_packed_target_opening_proof_payload_shape( proof_commitments: &CommitmentPayload, opening_proof: &AkitaPackingBatchProof, ) -> Result<(), VerifierError> { @@ -137,29 +137,30 @@ pub(crate) fn validate_akita_opening_proof_payload_shape( expected: PcsFamily::Lattice, got: proof_commitments.family(), })?; - if opening_proof.native.commitment() != &payload.packed_witness { + let native = opening_proof.native(); + if native.commitment() != &payload.packed_witness { return Err(VerifierError::InvalidProtocolConfig { reason: "lattice packed opening proof commitment does not match packed witness payload" .to_string(), }); } - validate_akita_commitment_bytes(opening_proof.native.commitment())?; - if opening_proof.native.statement_bridge().is_empty() { + validate_akita_commitment_bytes(native.commitment())?; + if native.statement_bridge().is_empty() { return Err(VerifierError::InvalidProtocolConfig { reason: "Akita opening proof is missing statement bridge bytes".to_string(), }); } - if opening_proof.native.proof_shape().is_empty() { + if native.proof_shape().is_empty() { return Err(VerifierError::InvalidProtocolConfig { reason: "Akita opening proof is missing native proof shape bytes".to_string(), }); } - if opening_proof.native.proof_bytes().is_empty() { + if native.proof_bytes().is_empty() { return Err(VerifierError::InvalidProtocolConfig { reason: "Akita opening proof is missing native proof bytes".to_string(), }); } - if let Some(reduction) = &opening_proof.reduction { + if let Some(reduction) = opening_proof.reduction() { validate_akita_field_bytes( "lattice packing reduction opening eval", &reduction.opening_eval, @@ -178,8 +179,8 @@ pub(crate) fn validate_akita_packing_opening_proof_payload_shape( opening_proof: &AkitaPackingBatchProof, field: &'static str, ) -> Result<(), VerifierError> { - validate_akita_opening_proof_payload_shape(proof_commitments, opening_proof)?; - if opening_proof.reduction.is_none() { + validate_akita_packed_target_opening_proof_payload_shape(proof_commitments, opening_proof)?; + if !matches!(opening_proof, PackingBatchProof::Packed { .. }) { return Err(VerifierError::InvalidProtocolConfig { reason: format!("{field} must include a packed reduction"), }); @@ -208,29 +209,28 @@ fn validate_akita_precommitted_opening_proof_payload_shape( expected: PcsFamily::Lattice, got: proof_commitments.family(), })?; - if opening_proof.native.commitment() == &payload.packed_witness { + let PackingBatchProof::Direct { native } = opening_proof else { + return Err(VerifierError::InvalidProtocolConfig { + reason: "lattice precommitted opening proof must be a direct native proof".to_string(), + }); + }; + if native.commitment() == &payload.packed_witness { return Err(VerifierError::InvalidProtocolConfig { reason: "lattice precommitted opening proof must target a separate precommitted commitment" .to_string(), }); } - if opening_proof.reduction.is_some() { - return Err(VerifierError::InvalidProtocolConfig { - reason: "lattice precommitted opening proof must not include a packed reduction" - .to_string(), - }); - } - if opening_proof.native.commitment().num_vars() != payload.d_pack { + if native.commitment().num_vars() != payload.d_pack { return Err(VerifierError::InvalidProtocolConfig { reason: format!( "lattice precommitted opening commitment dimension {} does not match Akita setup dimension {}", - opening_proof.native.commitment().num_vars(), + native.commitment().num_vars(), payload.d_pack ), }); } - validate_akita_native_opening_proof_payload_shape(&opening_proof.native) + validate_akita_native_opening_proof_payload_shape(native) } fn validate_akita_native_opening_proof_payload_shape( @@ -802,10 +802,8 @@ mod tests { 1, payload.packed_witness.native_bytes().to_vec(), ); - let proof = AkitaPackingBatchProof { - reduction: None, - native: native_opening_proof_with_commitment(precommitted), - }; + let proof = + AkitaPackingBatchProof::direct(native_opening_proof_with_commitment(precommitted)); assert!(matches!( validate_akita_precommitted_opening_proof_payload_shapes( diff --git a/crates/jolt-verifier/src/stages/stage8/precommitted.rs b/crates/jolt-verifier/src/stages/stage8/precommitted.rs index a4d85e5846..98975e0b38 100644 --- a/crates/jolt-verifier/src/stages/stage8/precommitted.rs +++ b/crates/jolt-verifier/src/stages/stage8/precommitted.rs @@ -1,9 +1,6 @@ #[cfg(test)] use super::verify::Stage8OpeningRoute; -use super::{ - outputs::{Stage8OpeningStatement, Stage8PhysicalOpening}, - verify::Stage8BatchEntry, -}; +use super::{outputs::Stage8OpeningStatement, verify::Stage8BatchEntry}; use crate::VerifierError; use jolt_field::Field; use jolt_openings::{ @@ -61,11 +58,6 @@ where let mut opening_claims = Vec::with_capacity(entries.len()); let mut statements = Vec::with_capacity(entries.len()); for entry in entries { - let physical = Stage8PhysicalOpening { - id: entry.id, - relation: entry.id, - view: PhysicalView::Direct, - }; let opening_claim = entry .opening_claim @@ -83,10 +75,10 @@ where layout_digest: direct_statement_layout_digest(entry.commitment, default_layout_digest), claims: vec![BatchOpeningClaim { id: entry.id, - relation: physical.relation, + relation: entry.id, commitment: entry.commitment.clone(), claim: opening_claim, - view: physical.view, + view: PhysicalView::Direct, scale: entry.scale, }], }); diff --git a/crates/jolt-verifier/src/stages/stage8/verify.rs b/crates/jolt-verifier/src/stages/stage8/verify.rs index dce42f9ed2..9d344514ff 100644 --- a/crates/jolt-verifier/src/stages/stage8/verify.rs +++ b/crates/jolt-verifier/src/stages/stage8/verify.rs @@ -215,16 +215,6 @@ where PCS: BatchOpeningScheme, T: Transcript, { - if batch.precommitted_statements.len() != precommitted_proofs.len() { - return Err(VerifierError::FinalOpeningVerificationFailed { - reason: format!( - "expected {} precommitted opening proofs, got {}", - batch.precommitted_statements.len(), - precommitted_proofs.len() - ), - }); - } - let precommitted_coefficients = verify_precommitted_opening_batches::( setup, transcript, From 7f549db358edcac09a57f1489871c21047e13d75 Mon Sep 17 00:00:00 2001 From: sumchecker <241190306+sumchecker@users.noreply.github.com> Date: Fri, 26 Jun 2026 11:36:43 -0700 Subject: [PATCH 5/7] verifier: fix warning-only CI failures Co-authored-by: Cursor --- crates/jolt-verifier/src/stages/stage8/precommitted.rs | 2 +- crates/jolt-verifier/src/verifier.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/jolt-verifier/src/stages/stage8/precommitted.rs b/crates/jolt-verifier/src/stages/stage8/precommitted.rs index 98975e0b38..31672e48b0 100644 --- a/crates/jolt-verifier/src/stages/stage8/precommitted.rs +++ b/crates/jolt-verifier/src/stages/stage8/precommitted.rs @@ -1,4 +1,4 @@ -#[cfg(test)] +#[cfg(all(test, feature = "akita"))] use super::verify::Stage8OpeningRoute; use super::{outputs::Stage8OpeningStatement, verify::Stage8BatchEntry}; use crate::VerifierError; diff --git a/crates/jolt-verifier/src/verifier.rs b/crates/jolt-verifier/src/verifier.rs index d35e457fe3..96afa9082e 100644 --- a/crates/jolt-verifier/src/verifier.rs +++ b/crates/jolt-verifier/src/verifier.rs @@ -2686,7 +2686,7 @@ mod tests { d_pack: usize, ) -> TestProof { let base = proof_with_zk(false, clear_claims()); - let mut proof = JoltProof::new_with_protocol( + let proof = JoltProof::new_with_protocol( *config, CommitmentPayload::Lattice(LatticeCommitmentPayload::new( TestCommitment, From b0882c349099e3006ab2320f0644aa0919c4a2a4 Mon Sep 17 00:00:00 2001 From: sumchecker <241190306+sumchecker@users.noreply.github.com> Date: Fri, 26 Jun 2026 11:49:54 -0700 Subject: [PATCH 6/7] jolt-akita: pin Akita main API update Co-authored-by: Cursor --- Cargo.lock | 28 +++--- crates/jolt-akita/Cargo.toml | 12 +-- crates/jolt-akita/src/batch.rs | 163 ++++++++++++++++++-------------- crates/jolt-akita/src/scheme.rs | 116 +++++++++++++---------- crates/jolt-akita/src/types.rs | 5 +- 5 files changed, 184 insertions(+), 140 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0090a3ac1f..28af0a7391 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -104,7 +104,7 @@ dependencies = [ [[package]] name = "akita-algebra" version = "0.1.0" -source = "git+https://github.com/LayerZero-Labs/akita.git?rev=15cd0069873aaed12fa3e3cb69d2108cfb88e95c#15cd0069873aaed12fa3e3cb69d2108cfb88e95c" +source = "git+https://github.com/LayerZero-Labs/akita.git?rev=b7b15524aac9f24b636358482f42dbc0fef1adc3#b7b15524aac9f24b636358482f42dbc0fef1adc3" dependencies = [ "akita-field", "akita-serialization", @@ -115,7 +115,7 @@ dependencies = [ [[package]] name = "akita-challenges" version = "0.1.0" -source = "git+https://github.com/LayerZero-Labs/akita.git?rev=15cd0069873aaed12fa3e3cb69d2108cfb88e95c#15cd0069873aaed12fa3e3cb69d2108cfb88e95c" +source = "git+https://github.com/LayerZero-Labs/akita.git?rev=b7b15524aac9f24b636358482f42dbc0fef1adc3#b7b15524aac9f24b636358482f42dbc0fef1adc3" dependencies = [ "akita-field", "akita-transcript", @@ -126,7 +126,7 @@ dependencies = [ [[package]] name = "akita-config" version = "0.1.0" -source = "git+https://github.com/LayerZero-Labs/akita.git?rev=15cd0069873aaed12fa3e3cb69d2108cfb88e95c#15cd0069873aaed12fa3e3cb69d2108cfb88e95c" +source = "git+https://github.com/LayerZero-Labs/akita.git?rev=b7b15524aac9f24b636358482f42dbc0fef1adc3#b7b15524aac9f24b636358482f42dbc0fef1adc3" dependencies = [ "akita-challenges", "akita-field", @@ -139,9 +139,10 @@ dependencies = [ [[package]] name = "akita-field" version = "0.1.0" -source = "git+https://github.com/LayerZero-Labs/akita.git?rev=15cd0069873aaed12fa3e3cb69d2108cfb88e95c#15cd0069873aaed12fa3e3cb69d2108cfb88e95c" +source = "git+https://github.com/LayerZero-Labs/akita.git?rev=b7b15524aac9f24b636358482f42dbc0fef1adc3#b7b15524aac9f24b636358482f42dbc0fef1adc3" dependencies = [ "akita-serialization", + "allocative", "jolt-field 0.1.0 (git+https://github.com/a16z/jolt?rev=2509bdcea9bb3e8978bb578380383ef06dbbada7)", "num-traits", "rand_core 0.6.4", @@ -151,7 +152,7 @@ dependencies = [ [[package]] name = "akita-pcs" version = "0.1.0" -source = "git+https://github.com/LayerZero-Labs/akita.git?rev=15cd0069873aaed12fa3e3cb69d2108cfb88e95c#15cd0069873aaed12fa3e3cb69d2108cfb88e95c" +source = "git+https://github.com/LayerZero-Labs/akita.git?rev=b7b15524aac9f24b636358482f42dbc0fef1adc3#b7b15524aac9f24b636358482f42dbc0fef1adc3" dependencies = [ "akita-algebra", "akita-challenges", @@ -170,7 +171,7 @@ dependencies = [ [[package]] name = "akita-planner" version = "0.1.0" -source = "git+https://github.com/LayerZero-Labs/akita.git?rev=15cd0069873aaed12fa3e3cb69d2108cfb88e95c#15cd0069873aaed12fa3e3cb69d2108cfb88e95c" +source = "git+https://github.com/LayerZero-Labs/akita.git?rev=b7b15524aac9f24b636358482f42dbc0fef1adc3#b7b15524aac9f24b636358482f42dbc0fef1adc3" dependencies = [ "akita-challenges", "akita-field", @@ -180,7 +181,7 @@ dependencies = [ [[package]] name = "akita-prover" version = "0.1.0" -source = "git+https://github.com/LayerZero-Labs/akita.git?rev=15cd0069873aaed12fa3e3cb69d2108cfb88e95c#15cd0069873aaed12fa3e3cb69d2108cfb88e95c" +source = "git+https://github.com/LayerZero-Labs/akita.git?rev=b7b15524aac9f24b636358482f42dbc0fef1adc3#b7b15524aac9f24b636358482f42dbc0fef1adc3" dependencies = [ "aes", "akita-algebra", @@ -191,7 +192,6 @@ dependencies = [ "akita-sumcheck", "akita-transcript", "akita-types", - "cfg-if", "ctr", "rand_core 0.6.4", "sha3 0.10.8", @@ -201,7 +201,7 @@ dependencies = [ [[package]] name = "akita-serialization" version = "0.1.0" -source = "git+https://github.com/LayerZero-Labs/akita.git?rev=15cd0069873aaed12fa3e3cb69d2108cfb88e95c#15cd0069873aaed12fa3e3cb69d2108cfb88e95c" +source = "git+https://github.com/LayerZero-Labs/akita.git?rev=b7b15524aac9f24b636358482f42dbc0fef1adc3#b7b15524aac9f24b636358482f42dbc0fef1adc3" dependencies = [ "thiserror 2.0.18", ] @@ -209,7 +209,7 @@ dependencies = [ [[package]] name = "akita-setup" version = "0.1.0" -source = "git+https://github.com/LayerZero-Labs/akita.git?rev=15cd0069873aaed12fa3e3cb69d2108cfb88e95c#15cd0069873aaed12fa3e3cb69d2108cfb88e95c" +source = "git+https://github.com/LayerZero-Labs/akita.git?rev=b7b15524aac9f24b636358482f42dbc0fef1adc3#b7b15524aac9f24b636358482f42dbc0fef1adc3" dependencies = [ "akita-algebra", "akita-config", @@ -223,7 +223,7 @@ dependencies = [ [[package]] name = "akita-sumcheck" version = "0.1.0" -source = "git+https://github.com/LayerZero-Labs/akita.git?rev=15cd0069873aaed12fa3e3cb69d2108cfb88e95c#15cd0069873aaed12fa3e3cb69d2108cfb88e95c" +source = "git+https://github.com/LayerZero-Labs/akita.git?rev=b7b15524aac9f24b636358482f42dbc0fef1adc3#b7b15524aac9f24b636358482f42dbc0fef1adc3" dependencies = [ "akita-algebra", "akita-field", @@ -235,7 +235,7 @@ dependencies = [ [[package]] name = "akita-transcript" version = "0.1.0" -source = "git+https://github.com/LayerZero-Labs/akita.git?rev=15cd0069873aaed12fa3e3cb69d2108cfb88e95c#15cd0069873aaed12fa3e3cb69d2108cfb88e95c" +source = "git+https://github.com/LayerZero-Labs/akita.git?rev=b7b15524aac9f24b636358482f42dbc0fef1adc3#b7b15524aac9f24b636358482f42dbc0fef1adc3" dependencies = [ "akita-field", "akita-serialization", @@ -246,7 +246,7 @@ dependencies = [ [[package]] name = "akita-types" version = "0.1.0" -source = "git+https://github.com/LayerZero-Labs/akita.git?rev=15cd0069873aaed12fa3e3cb69d2108cfb88e95c#15cd0069873aaed12fa3e3cb69d2108cfb88e95c" +source = "git+https://github.com/LayerZero-Labs/akita.git?rev=b7b15524aac9f24b636358482f42dbc0fef1adc3#b7b15524aac9f24b636358482f42dbc0fef1adc3" dependencies = [ "akita-algebra", "akita-challenges", @@ -264,7 +264,7 @@ dependencies = [ [[package]] name = "akita-verifier" version = "0.1.0" -source = "git+https://github.com/LayerZero-Labs/akita.git?rev=15cd0069873aaed12fa3e3cb69d2108cfb88e95c#15cd0069873aaed12fa3e3cb69d2108cfb88e95c" +source = "git+https://github.com/LayerZero-Labs/akita.git?rev=b7b15524aac9f24b636358482f42dbc0fef1adc3#b7b15524aac9f24b636358482f42dbc0fef1adc3" dependencies = [ "akita-algebra", "akita-challenges", diff --git a/crates/jolt-akita/Cargo.toml b/crates/jolt-akita/Cargo.toml index cb4be8610f..df71e1a3e8 100644 --- a/crates/jolt-akita/Cargo.toml +++ b/crates/jolt-akita/Cargo.toml @@ -9,14 +9,14 @@ description = "Akita batch opening adapter for the Jolt zkVM" workspace = true [dependencies] -akita-config = { git = "https://github.com/LayerZero-Labs/akita.git", rev = "15cd0069873aaed12fa3e3cb69d2108cfb88e95c", default-features = false } -akita-field = { git = "https://github.com/LayerZero-Labs/akita.git", rev = "15cd0069873aaed12fa3e3cb69d2108cfb88e95c", default-features = false, features = [ +akita-config = { git = "https://github.com/LayerZero-Labs/akita.git", rev = "b7b15524aac9f24b636358482f42dbc0fef1adc3", default-features = false } +akita-field = { git = "https://github.com/LayerZero-Labs/akita.git", rev = "b7b15524aac9f24b636358482f42dbc0fef1adc3", default-features = false, features = [ "jolt-compat", ] } -akita-pcs = { git = "https://github.com/LayerZero-Labs/akita.git", rev = "15cd0069873aaed12fa3e3cb69d2108cfb88e95c", default-features = false } -akita-prover = { git = "https://github.com/LayerZero-Labs/akita.git", rev = "15cd0069873aaed12fa3e3cb69d2108cfb88e95c", default-features = false } -akita-transcript = { git = "https://github.com/LayerZero-Labs/akita.git", rev = "15cd0069873aaed12fa3e3cb69d2108cfb88e95c", default-features = false } -akita-types = { git = "https://github.com/LayerZero-Labs/akita.git", rev = "15cd0069873aaed12fa3e3cb69d2108cfb88e95c", default-features = false } +akita-pcs = { git = "https://github.com/LayerZero-Labs/akita.git", rev = "b7b15524aac9f24b636358482f42dbc0fef1adc3", default-features = false } +akita-prover = { git = "https://github.com/LayerZero-Labs/akita.git", rev = "b7b15524aac9f24b636358482f42dbc0fef1adc3", default-features = false } +akita-transcript = { git = "https://github.com/LayerZero-Labs/akita.git", rev = "b7b15524aac9f24b636358482f42dbc0fef1adc3", default-features = false } +akita-types = { git = "https://github.com/LayerZero-Labs/akita.git", rev = "b7b15524aac9f24b636358482f42dbc0fef1adc3", default-features = false } jolt-crypto.workspace = true jolt-field.workspace = true jolt-openings.workspace = true diff --git a/crates/jolt-akita/src/batch.rs b/crates/jolt-akita/src/batch.rs index bbf6bca228..232b2ada7e 100644 --- a/crates/jolt-akita/src/batch.rs +++ b/crates/jolt-akita/src/batch.rs @@ -1,7 +1,10 @@ -use akita_pcs::{AkitaTranscript, CommitmentProver, CpuBackend}; -use akita_prover::{AkitaPolyOps, CommittedPolynomials, ProverClaims}; +use akita_pcs::{ + AkitaTranscript, CommitmentProver, CpuBackend, ProverCommitmentGroup, ProverOpeningBatch, + RootPolyShape, +}; use akita_types::{ - BasisMode, CommitmentVerifier, CommittedOpenings, SetupContributionMode, VerifierClaims, + BasisMode, CommitmentGroup, CommitmentVerifier, PointVariableSelection, SetupContributionMode, + VerifierOpeningBatch, }; use jolt_openings::{ BatchOpeningResult, BatchOpeningScheme, BatchOpeningStatement, OpeningsError, PhysicalView, @@ -17,8 +20,8 @@ use crate::{ }, types::{ AkitaBatchProof, AkitaCommitment, AkitaField, AkitaProverHint, AkitaProverSetup, - AkitaVerifierSetup, NativeCommitment, NativeHint, NativeProof, NativeProofShape, - NativeScheme, NativeVerifier, AKITA_D, + AkitaVerifierSetup, NativeCommitment, NativeDensePoly, NativeProof, NativeProofShape, + NativeScheme, NativeSparsePoly, NativeVerifier, AKITA_D, }, }; @@ -34,14 +37,7 @@ impl BatchOpeningScheme for AkitaScheme { T: Transcript, { let dense = dense_polynomials(polynomials)?; - let dense_refs = dense.iter().collect::>(); - prove_batch_with_native_polynomials( - setup, - transcript, - statement, - dense_refs.as_slice(), - hints, - ) + prove_batch_with_native_polynomials(setup, transcript, statement, dense.as_slice(), hints) } fn verify_batch( @@ -83,13 +79,14 @@ impl BatchOpeningScheme for AkitaScheme { .iter() .map(|claim| claim.claim) .collect::>(); - let claims: VerifierClaims<'_, AkitaField, _> = ( - statement.pcs_point.as_slice(), - vec![CommittedOpenings { - openings: openings.as_slice(), + let claims = VerifierOpeningBatch::from_groups( + statement.pcs_point.clone(), + vec![CommitmentGroup { + claims: openings, commitment: &native_commitment, }], - ); + ) + .map_err(akita_error)?; NativeScheme::batched_verify( &native_proof, &native_verifier, @@ -108,65 +105,93 @@ impl BatchOpeningScheme for AkitaScheme { } } -pub(crate) fn prove_batch_with_native_polynomials( +macro_rules! prove_native_batch { + ($setup:expr, $transcript:expr, $statement:expr, $polynomials:expr, $hints:expr) => {{ + let normalized = normalize_clear_batch($statement)?; + validate_native_prover_inputs($setup, &normalized.commitment, $polynomials, &$hints)?; + bind_verifier_setup_key(&$setup.verifier, $transcript); + bind_batch_statement( + $statement, + &normalized.commitment, + &normalized.coefficients, + normalized.reduced_opening, + $transcript, + ); + let mut akita_transcript = AkitaTranscript::::new(b"jolt-akita/batch"); + let statement_bridge = bind_jolt_transcript_bridge($transcript, &mut akita_transcript); + + let native_commitment = + deserialize_akita::(&normalized.commitment.native, &())?; + let native_hint = $hints + .into_iter() + .next() + .and_then(|hint| hint.native) + .ok_or_else(|| invalid_batch("Akita prover hint is missing native opening data"))?; + let poly_refs = $polynomials.iter().collect::>(); + let claims = ProverOpeningBatch { + point: $statement.pcs_point.as_slice().into(), + groups: vec![ProverCommitmentGroup { + point_vars: PointVariableSelection::prefix( + $statement.pcs_point.len(), + $statement.pcs_point.len(), + ) + .map_err(akita_error)?, + polynomials: poly_refs.as_slice(), + commitment: (native_commitment, native_hint), + }], + }; + let stack = akita_prover::UniformProverStack::uniform( + &CpuBackend, + &$setup.prepared, + $setup.native.expanded.as_ref(), + ) + .map_err(akita_error)?; + + let native_proof = NativeScheme::batched_prove( + &$setup.native, + claims, + &stack, + &mut akita_transcript, + BasisMode::Lagrange, + SetupContributionMode::Direct, + ) + .map_err(akita_error)?; + let proof_shape = native_proof.shape(); + let proof = AkitaBatchProof { + commitment: normalized.commitment, + statement_bridge, + proof_shape: serialize_akita(&proof_shape)?, + proof: serialize_akita(&native_proof)?, + }; + bind_proof_bytes(&proof, $transcript); + Ok(proof) + }}; +} + +pub(crate) fn prove_batch_with_native_polynomials( setup: &AkitaProverSetup, transcript: &mut T, statement: &BatchOpeningStatement, - polynomials: &[P], + polynomials: &[NativeDensePoly], hints: Vec, ) -> Result where T: Transcript, - P: AkitaPolyOps, { - let normalized = normalize_clear_batch(statement)?; - validate_native_prover_inputs(setup, &normalized.commitment, polynomials, &hints)?; - bind_verifier_setup_key(&setup.verifier, transcript); - bind_batch_statement( - statement, - &normalized.commitment, - &normalized.coefficients, - normalized.reduced_opening, - transcript, - ); - let mut akita_transcript = AkitaTranscript::::new(b"jolt-akita/batch"); - let statement_bridge = bind_jolt_transcript_bridge(transcript, &mut akita_transcript); - - let native_commitment = - deserialize_akita::(&normalized.commitment.native, &())?; - let native_hint = hints - .into_iter() - .next() - .and_then(|hint| hint.native) - .ok_or_else(|| invalid_batch("Akita prover hint is missing native opening data"))?; - let claims: ProverClaims<'_, AkitaField, P, _, NativeHint> = ( - statement.pcs_point.as_slice(), - vec![CommittedPolynomials { - polynomials, - commitment: &native_commitment, - hint: native_hint, - }], - ); + prove_native_batch!(setup, transcript, statement, polynomials, hints) +} - let native_proof = NativeScheme::batched_prove( - &setup.native, - &CpuBackend, - &setup.prepared, - claims, - &mut akita_transcript, - BasisMode::Lagrange, - SetupContributionMode::Direct, - ) - .map_err(akita_error)?; - let proof_shape = native_proof.shape(); - let proof = AkitaBatchProof { - commitment: normalized.commitment, - statement_bridge, - proof_shape: serialize_akita(&proof_shape)?, - proof: serialize_akita(&native_proof)?, - }; - bind_proof_bytes(&proof, transcript); - Ok(proof) +pub(crate) fn prove_sparse_batch_with_native_polynomials( + setup: &AkitaProverSetup, + transcript: &mut T, + statement: &BatchOpeningStatement, + polynomials: &[NativeSparsePoly], + hints: Vec, +) -> Result +where + T: Transcript, +{ + prove_native_batch!(setup, transcript, statement, polynomials, hints) } struct NormalizedBatch { @@ -238,7 +263,7 @@ fn validate_native_prover_inputs

( hints: &[AkitaProverHint], ) -> Result<(), OpeningsError> where - P: AkitaPolyOps, + P: RootPolyShape, { validate_setup_shape( setup.max_num_vars, diff --git a/crates/jolt-akita/src/scheme.rs b/crates/jolt-akita/src/scheme.rs index 0355f8c89d..ff65754fd1 100644 --- a/crates/jolt-akita/src/scheme.rs +++ b/crates/jolt-akita/src/scheme.rs @@ -1,5 +1,4 @@ -use akita_pcs::{CommitmentProver, ComputeBackendSetup, CpuBackend}; -use akita_prover::AkitaPolyOps; +use akita_pcs::{CommitmentProver, ComputeBackendSetup, CpuBackend, RootPolyShape}; use jolt_crypto::Commitment; use jolt_openings::{ BatchOpeningScheme, BatchOpeningStatement, CommitmentScheme, OpeningsError, PhysicalView, @@ -8,14 +7,14 @@ use jolt_poly::{MultilinearPoly, Polynomial}; use jolt_transcript::{AppendToTranscript, Label, Transcript}; use serde::{Deserialize, Serialize}; -use crate::batch::prove_batch_with_native_polynomials; +use crate::batch::prove_sparse_batch_with_native_polynomials; use crate::native::{ akita_error, dense_polynomials, invalid_batch, polynomial_evaluations, serialize_akita, }; use crate::types::{ append_field_slice, AkitaBatchProof, AkitaCommitment, AkitaField, AkitaProverHint, - AkitaProverSetup, AkitaSetupParams, AkitaSparsePolynomial, AkitaVerifierSetup, NativeScheme, - AKITA_D, + AkitaProverSetup, AkitaSetupParams, AkitaSparsePolynomial, AkitaVerifierSetup, NativeDensePoly, + NativeScheme, NativeSparsePoly, AKITA_D, }; #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -38,13 +37,12 @@ impl AkitaScheme { ) -> Result<(AkitaCommitment, AkitaProverHint), OpeningsError> { let num_vars = validate_commit_polynomials(setup, polynomials)?; let dense = dense_polynomials(polynomials)?; - let dense_refs = dense.iter().collect::>(); - commit_native_group( + commit_dense_native_group( setup, layout_digest, num_vars, polynomials.len(), - dense_refs.as_slice(), + dense.as_slice(), ) } @@ -53,12 +51,12 @@ impl AkitaScheme { layout_digest: [u8; 32], polynomial: &AkitaSparsePolynomial, ) -> Result<(AkitaCommitment, AkitaProverHint), OpeningsError> { - commit_native_group( + commit_sparse_native_group( setup, layout_digest, polynomial.num_vars(), 1, - &[&polynomial.native], + std::slice::from_ref(&polynomial.native), ) } @@ -85,11 +83,11 @@ impl AkitaScheme { where T: Transcript, { - prove_batch_with_native_polynomials( + prove_sparse_batch_with_native_polynomials( setup, transcript, statement, - &[&polynomial.native], + std::slice::from_ref(&polynomial.native), vec![hint], ) } @@ -112,48 +110,68 @@ impl AkitaScheme { } } -fn commit_native_group

( +macro_rules! commit_native_group { + ($setup:expr, $layout_digest:expr, $num_vars:expr, $poly_count:expr, $polynomials:expr) => {{ + validate_native_commit_shape($setup, $num_vars, $poly_count)?; + if $polynomials.len() != $poly_count { + return Err(invalid_batch(format!( + "Akita native commit received {} polynomials for {} commitment slots", + $polynomials.len(), + $poly_count + ))); + } + for polynomial in $polynomials { + if polynomial.num_vars() != $num_vars { + return Err(invalid_batch(format!( + "Akita native commit mixes {}-variable and {}-variable polynomials", + polynomial.num_vars(), + $num_vars + ))); + } + } + + let stack = akita_prover::UniformProverStack::uniform( + &CpuBackend, + &$setup.prepared, + $setup.native.expanded.as_ref(), + ) + .map_err(akita_error)?; + let (native_commitment, native_hint) = + NativeScheme::commit(&$setup.native, $polynomials, &stack).map_err(akita_error)?; + let commitment = AkitaCommitment { + layout_digest: $layout_digest, + num_vars: $num_vars, + poly_count: $poly_count, + native: serialize_akita(&native_commitment)?, + }; + Ok(( + commitment.clone(), + AkitaProverHint { + commitment, + native: Some(native_hint), + }, + )) + }}; +} + +fn commit_dense_native_group( setup: &AkitaProverSetup, layout_digest: [u8; 32], num_vars: usize, poly_count: usize, - polynomials: &[P], -) -> Result<(AkitaCommitment, AkitaProverHint), OpeningsError> -where - P: AkitaPolyOps, -{ - validate_native_commit_shape(setup, num_vars, poly_count)?; - if polynomials.len() != poly_count { - return Err(invalid_batch(format!( - "Akita native commit received {} polynomials for {poly_count} commitment slots", - polynomials.len() - ))); - } - for polynomial in polynomials { - if polynomial.num_vars() != num_vars { - return Err(invalid_batch(format!( - "Akita native commit mixes {}-variable and {num_vars}-variable polynomials", - polynomial.num_vars() - ))); - } - } + polynomials: &[NativeDensePoly], +) -> Result<(AkitaCommitment, AkitaProverHint), OpeningsError> { + commit_native_group!(setup, layout_digest, num_vars, poly_count, polynomials) +} - let (native_commitment, native_hint) = - NativeScheme::commit(&setup.native, &CpuBackend, &setup.prepared, polynomials) - .map_err(akita_error)?; - let commitment = AkitaCommitment { - layout_digest, - num_vars, - poly_count, - native: serialize_akita(&native_commitment)?, - }; - Ok(( - commitment.clone(), - AkitaProverHint { - commitment, - native: Some(native_hint), - }, - )) +fn commit_sparse_native_group( + setup: &AkitaProverSetup, + layout_digest: [u8; 32], + num_vars: usize, + poly_count: usize, + polynomials: &[NativeSparsePoly], +) -> Result<(AkitaCommitment, AkitaProverHint), OpeningsError> { + commit_native_group!(setup, layout_digest, num_vars, poly_count, polynomials) } fn validate_native_commit_shape( diff --git a/crates/jolt-akita/src/types.rs b/crates/jolt-akita/src/types.rs index ca8c14ff35..e07d6a7278 100644 --- a/crates/jolt-akita/src/types.rs +++ b/crates/jolt-akita/src/types.rs @@ -3,7 +3,7 @@ use std::collections::BTreeSet; use akita_config::CommitmentConfig; use akita_field::PseudoMersenneField; use akita_pcs::AkitaCommitmentScheme; -use akita_prover::{AkitaPolyOps, CpuPreparedSetup, DensePoly, SparseRingPoly}; +use akita_prover::{CpuPreparedSetup, DensePoly, SparseRingPoly}; use akita_types::{ AkitaBatchedProof as NativeBatchProof, AkitaBatchedProofShape, AkitaCommitmentHint as NativeCommitmentHint, AkitaVerifierSetup as NativeVerifierSetup, @@ -16,13 +16,14 @@ use serde::{Deserialize, Serialize}; pub type AkitaField = akita_config::proof_optimized::fp128::Field; pub(crate) type AkitaConfig = akita_config::proof_optimized::fp128::D64Full; pub(crate) const AKITA_D: usize = AkitaConfig::D; +pub(crate) type NativeExtField = ::ExtField; pub const AKITA_FIELD_MODULUS: u128 = u128::MAX - (::MODULUS_OFFSET - 1); pub(crate) type NativeScheme = AkitaCommitmentScheme; pub(crate) type NativeCommitment = NativeRingCommitment; pub(crate) type NativeHint = NativeCommitmentHint; -pub(crate) type NativeProof = NativeBatchProof; +pub(crate) type NativeProof = NativeBatchProof; pub(crate) type NativeProofShape = AkitaBatchedProofShape; pub(crate) type NativeVerifier = NativeVerifierSetup; pub(crate) type NativeDensePoly = DensePoly; From 411668bb591e71c344604840b3d5acaf51ce23d8 Mon Sep 17 00:00:00 2001 From: sumchecker <241190306+sumchecker@users.noreply.github.com> Date: Fri, 26 Jun 2026 11:55:48 -0700 Subject: [PATCH 7/7] verifier: handle Akita-only fixture mutation Co-authored-by: Cursor --- crates/jolt-verifier/src/verifier.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/jolt-verifier/src/verifier.rs b/crates/jolt-verifier/src/verifier.rs index 96afa9082e..78a92467b9 100644 --- a/crates/jolt-verifier/src/verifier.rs +++ b/crates/jolt-verifier/src/verifier.rs @@ -2686,7 +2686,14 @@ mod tests { d_pack: usize, ) -> TestProof { let base = proof_with_zk(false, clear_claims()); - let proof = JoltProof::new_with_protocol( + #[cfg_attr( + not(feature = "akita"), + expect( + unused_mut, + reason = "proof is mutated only when Akita validity is attached" + ) + )] + let mut proof = JoltProof::new_with_protocol( *config, CommitmentPayload::Lattice(LatticeCommitmentPayload::new( TestCommitment,