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/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..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,20 +7,29 @@ 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)] 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], @@ -29,31 +37,43 @@ 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(), ) } - pub fn commit_sparse_polynomial( + pub(crate) fn commit_sparse_polynomial( setup: &AkitaProverSetup, 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), ) } - 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, @@ -63,58 +83,95 @@ 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], ) } + + /// 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

( +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/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..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, @@ -14,29 +14,30 @@ 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(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; 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 +79,7 @@ impl AkitaSparsePolynomial { }) } - pub fn num_vars(&self) -> usize { + pub(crate) fn num_vars(&self) -> usize { self.native.num_vars() } } @@ -97,9 +98,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 +115,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 +230,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 +279,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-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 d54fd80608..77efafcfdd 100644 --- a/crates/jolt-verifier/src/akita_openings.rs +++ b/crates/jolt-verifier/src/akita_openings.rs @@ -124,13 +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 packed = - prove_akita_packing_openings(setup, transcript, artifacts, source, &statement.statement)?; let precommitted = prove_akita_precommitted_opening_batches( setup, transcript, @@ -138,6 +131,8 @@ where &statement.precommitted_statements, precommitted_inputs, )?; + let packed = + prove_akita_packing_openings(setup, transcript, artifacts, source, &statement.statement)?; Ok(AkitaStage8ClearOpeningProofs { packed, precommitted, @@ -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, }, @@ -427,7 +422,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 +480,49 @@ 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 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, @@ -594,14 +632,15 @@ 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(); 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,9 +656,19 @@ mod tests { )); let mut wrong_commitment_proof = proof.clone(); - wrong_commitment_proof.native.commitment.layout_digest = [9; 32]; + 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, ), @@ -628,15 +677,21 @@ mod tests { )); let mut missing_native_proof = proof.clone(); - missing_native_proof.native.proof.clear(); + 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, @@ -647,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, ), @@ -663,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, @@ -769,7 +825,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 { @@ -808,8 +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.commitment = packed_commitment.clone(); + let packed_target_precommitted_proof = AkitaPackingBatchProof::direct(proof_with_parts( + proofs.precommitted[0].native(), + packed_commitment.clone(), + proofs.precommitted[0].native().proof_bytes().to_vec(), + )); assert!(matches!( validate_akita_precommitted_opening_proof_payload_shapes( &artifact.commitments, @@ -819,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(); @@ -878,17 +939,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..01723b893b 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,20 +47,18 @@ 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 { - reduction: None, - native, - }); + return Ok(PackingBatchProof::direct(native)); } let shape = validate_packed_source_prover_inputs(setup, statement, source, &hint)?; @@ -72,17 +70,15 @@ 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 { - reduction: Some(reduction.proof), - native, - }); + return Ok(PackingBatchProof::packed(reduction.proof, native)); } let polynomial = packing_witness_source_polynomial(source)?; @@ -136,14 +132,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 +147,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 +208,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 +249,7 @@ where }); result?; - AkitaSparsePolynomial::from_jolt_unit_indices(layout.dimension, ranks).map(Some) + Ok(Some(ranks)) } fn singleton_statement( @@ -282,7 +260,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..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, @@ -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(), @@ -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.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,37 +209,45 @@ 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() { + if native.commitment().num_vars() != payload.d_pack { return Err(VerifierError::InvalidProtocolConfig { - reason: "lattice precommitted opening proof must not include a packed reduction" - .to_string(), + reason: format!( + "lattice precommitted opening commitment dimension {} does not match Akita setup dimension {}", + 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( 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,29 @@ 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::direct(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..31672e48b0 100644 --- a/crates/jolt-verifier/src/stages/stage8/precommitted.rs +++ b/crates/jolt-verifier/src/stages/stage8/precommitted.rs @@ -1,14 +1,13 @@ -use super::{ - outputs::{Stage8OpeningStatement, Stage8PhysicalOpening}, - verify::{clear_batch_claims, Stage8BatchEntry}, -}; +#[cfg(all(test, feature = "akita"))] +use super::verify::Stage8OpeningRoute; +use super::{outputs::Stage8OpeningStatement, 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 +50,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, @@ -61,22 +58,29 @@ 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 (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: entry.id, + commitment: entry.commitment.clone(), + claim: opening_claim, + view: PhysicalView::Direct, + scale: entry.scale, + }], }); } @@ -106,6 +110,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 +293,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, @@ -300,18 +307,26 @@ mod tests { let entry = Stage8BatchEntry { id, commitment: &commitment, + route: Stage8OpeningRoute::Precommitted, 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..9a972676dd 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]>, @@ -374,6 +402,7 @@ where trusted_advice_commitment, polynomial, ) + .map(Stage8EntryTarget::main_batch) }, #[cfg(feature = "field-inline")] &commitments.field_inline.field_registers.rd_inc, @@ -400,8 +429,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")] @@ -415,7 +445,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) } }; @@ -489,12 +519,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, @@ -567,7 +593,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, @@ -578,12 +604,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 @@ -686,10 +712,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), @@ -700,6 +727,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 @@ -750,6 +778,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), @@ -758,6 +787,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), @@ -846,23 +876,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, @@ -1234,7 +1247,7 @@ mod tests { &program_image_only, None, true, - |_| Ok(&commitment), + |_| Ok(Stage8EntryTarget::main_batch(&commitment)), #[cfg(feature = "field-inline")] &commitment, ) @@ -1262,7 +1275,7 @@ mod tests { &bytecode_only, None, true, - |_| Ok(&commitment), + |_| Ok(Stage8EntryTarget::main_batch(&commitment)), #[cfg(feature = "field-inline")] &commitment, ) diff --git a/crates/jolt-verifier/src/verifier.rs b/crates/jolt-verifier/src/verifier.rs index 944377dc24..78a92467b9 100644 --- a/crates/jolt-verifier/src/verifier.rs +++ b/crates/jolt-verifier/src/verifier.rs @@ -2685,19 +2685,34 @@ 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()); + #[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, + 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)