diff --git a/crates/jolt-dory-assist-verifier/Cargo.toml b/crates/jolt-dory-assist-verifier/Cargo.toml new file mode 100644 index 0000000000..6dc8dbc8f1 --- /dev/null +++ b/crates/jolt-dory-assist-verifier/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "jolt-dory-assist-verifier" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "Dory-assist verifier implementation for Jolt PCS assist" + +[lints] +workspace = true + +[dependencies] +jolt-crypto = { workspace = true, features = ["grumpkin"] } +jolt-claims = { workspace = true } +jolt-dory = { path = "../jolt-dory" } +jolt-field = { workspace = true } +jolt-hyrax = { workspace = true } +jolt-openings = { workspace = true } +jolt-poly = { workspace = true } +jolt-sumcheck = { workspace = true } +jolt-transcript = { workspace = true } +jolt-verifier = { workspace = true } +serde = { workspace = true, features = ["derive"] } +thiserror = { workspace = true } diff --git a/crates/jolt-dory-assist-verifier/src/artifacts.rs b/crates/jolt-dory-assist-verifier/src/artifacts.rs new file mode 100644 index 0000000000..5a53b97fa5 --- /dev/null +++ b/crates/jolt-dory-assist-verifier/src/artifacts.rs @@ -0,0 +1,294 @@ +//! Layout helpers for public Dory proof artifacts staged into `Fq`. + +use std::ops::Range; + +use jolt_dory::DoryProof; +use jolt_field::Fq; + +pub use jolt_claims::protocols::dory_assist::formulas::artifacts::{ + DORY_PROOF_DIGEST_INDEX, DORY_REDUCE_ROUNDS_START, DORY_SCALAR_PRODUCT_E1_START, + DORY_SCALAR_PRODUCT_E2_START, DORY_SCALAR_PRODUCT_P1_START, DORY_SCALAR_PRODUCT_P2_START, + DORY_SCALAR_PRODUCT_Q_START, DORY_SCALAR_PRODUCT_R1_INDEX, DORY_SCALAR_PRODUCT_R2_INDEX, + DORY_SCALAR_PRODUCT_R3_INDEX, DORY_SCALAR_PRODUCT_R_START, DORY_VMV_C_START, DORY_VMV_D2_START, + DORY_VMV_E1_START, DORY_ZK_E2_START, DORY_ZK_Y_COM_START, FIRST_REDUCE_ARTIFACT_COORDS, + G1_ARTIFACT_COORDS, G2_ARTIFACT_COORDS, GT_ARTIFACT_COEFFS, REDUCE_ROUND_ARTIFACT_COORDS, + SECOND_REDUCE_ARTIFACT_COORDS, +}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct DoryProofArtifactLayout { + reduce_rounds: usize, +} + +impl DoryProofArtifactLayout { + pub const fn new(reduce_rounds: usize) -> Self { + Self { reduce_rounds } + } + + pub fn for_proof(proof: &DoryProof) -> Self { + Self::new(proof.reduce_round_count()) + } + + pub const fn reduce_rounds(self) -> usize { + self.reduce_rounds + } + + pub const fn expected_len(self) -> usize { + self.final_e2_start() + G2_ARTIFACT_COORDS + } + + pub fn vmv_c(self) -> Range { + gt_range(DORY_VMV_C_START) + } + + pub fn vmv_d2(self) -> Range { + gt_range(DORY_VMV_D2_START) + } + + pub fn vmv_e1(self) -> Range { + g1_range(DORY_VMV_E1_START) + } + + pub fn zk_e2(self) -> Range { + g2_range(DORY_ZK_E2_START) + } + + pub fn zk_y_com(self) -> Range { + g1_range(DORY_ZK_Y_COM_START) + } + + pub fn scalar_product_p1(self) -> Range { + gt_range(DORY_SCALAR_PRODUCT_P1_START) + } + + pub fn scalar_product_p2(self) -> Range { + gt_range(DORY_SCALAR_PRODUCT_P2_START) + } + + pub fn scalar_product_q(self) -> Range { + gt_range(DORY_SCALAR_PRODUCT_Q_START) + } + + pub fn scalar_product_r(self) -> Range { + gt_range(DORY_SCALAR_PRODUCT_R_START) + } + + pub fn scalar_product_e1(self) -> Range { + g1_range(DORY_SCALAR_PRODUCT_E1_START) + } + + pub fn scalar_product_e2(self) -> Range { + g2_range(DORY_SCALAR_PRODUCT_E2_START) + } + + pub const fn scalar_product_r1(self) -> usize { + let _ = self; + DORY_SCALAR_PRODUCT_R1_INDEX + } + + pub const fn scalar_product_r2(self) -> usize { + let _ = self; + DORY_SCALAR_PRODUCT_R2_INDEX + } + + pub const fn scalar_product_r3(self) -> usize { + let _ = self; + DORY_SCALAR_PRODUCT_R3_INDEX + } + + pub const fn reduce_round_start(self, round: usize) -> usize { + DORY_REDUCE_ROUNDS_START + round * REDUCE_ROUND_ARTIFACT_COORDS + } + + pub fn reduce_round(self, round: usize) -> DoryReduceRoundArtifactRanges { + DoryReduceRoundArtifactRanges::new(self.reduce_round_start(round)) + } + + pub const fn final_e1_start(self) -> usize { + DORY_REDUCE_ROUNDS_START + self.reduce_rounds * REDUCE_ROUND_ARTIFACT_COORDS + } + + pub const fn final_e2_start(self) -> usize { + self.final_e1_start() + G1_ARTIFACT_COORDS + } + + pub fn final_e1(self) -> Range { + g1_range(self.final_e1_start()) + } + + pub fn final_e2(self) -> Range { + g2_range(self.final_e2_start()) + } + + pub fn gt_at(self, artifacts: &[Fq], range: Range) -> Option<[Fq; GT_ARTIFACT_COEFFS]> { + let _ = self; + copy_artifact(artifacts, range) + } + + pub fn g1_at(self, artifacts: &[Fq], range: Range) -> Option<[Fq; G1_ARTIFACT_COORDS]> { + let _ = self; + copy_artifact(artifacts, range) + } + + pub fn g2_at(self, artifacts: &[Fq], range: Range) -> Option<[Fq; G2_ARTIFACT_COORDS]> { + let _ = self; + copy_artifact(artifacts, range) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct DoryReduceRoundArtifactRanges { + start: usize, +} + +impl DoryReduceRoundArtifactRanges { + pub const fn new(start: usize) -> Self { + Self { start } + } + + pub fn first_d1_left(self) -> Range { + gt_range(self.start) + } + + pub fn first_d1_right(self) -> Range { + gt_range(self.first_d1_left().end) + } + + pub fn first_d2_left(self) -> Range { + gt_range(self.first_d1_right().end) + } + + pub fn first_d2_right(self) -> Range { + gt_range(self.first_d2_left().end) + } + + pub fn first_e1_beta(self) -> Range { + g1_range(self.first_d2_right().end) + } + + pub fn first_e2_beta(self) -> Range { + g2_range(self.first_e1_beta().end) + } + + pub fn second_c_plus(self) -> Range { + gt_range(self.first_e2_beta().end) + } + + pub fn second_c_minus(self) -> Range { + gt_range(self.second_c_plus().end) + } + + pub fn second_e1_plus(self) -> Range { + g1_range(self.second_c_minus().end) + } + + pub fn second_e1_minus(self) -> Range { + g1_range(self.second_e1_plus().end) + } + + pub fn second_e2_plus(self) -> Range { + g2_range(self.second_e1_minus().end) + } + + pub fn second_e2_minus(self) -> Range { + g2_range(self.second_e2_plus().end) + } + + pub fn full(self) -> Range { + self.start..self.second_e2_minus().end + } +} + +pub fn gt_range(start: usize) -> Range { + start..start + GT_ARTIFACT_COEFFS +} + +pub fn g1_range(start: usize) -> Range { + start..start + G1_ARTIFACT_COORDS +} + +pub fn g2_range(start: usize) -> Range { + start..start + G2_ARTIFACT_COORDS +} + +pub fn copy_artifact(artifacts: &[Fq], range: Range) -> Option<[Fq; N]> { + if range.len() != N { + return None; + } + let slice = artifacts.get(range)?; + let mut artifact = [Fq::default(); N]; + artifact.copy_from_slice(slice); + Some(artifact) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn fixed_artifact_ranges_are_contiguous() { + let layout = DoryProofArtifactLayout::new(0); + + assert_eq!(layout.vmv_c(), 1..17); + assert_eq!(layout.vmv_d2(), 17..33); + assert_eq!(layout.vmv_e1(), 33..36); + assert_eq!(layout.zk_e2(), 36..41); + assert_eq!(layout.zk_y_com(), 41..44); + assert_eq!(layout.scalar_product_p1(), 44..60); + assert_eq!(layout.scalar_product_p2(), 60..76); + assert_eq!(layout.scalar_product_q(), 76..92); + assert_eq!(layout.scalar_product_r(), 92..108); + assert_eq!(layout.scalar_product_e1(), 108..111); + assert_eq!(layout.scalar_product_e2(), 111..116); + assert_eq!(layout.scalar_product_r1(), 116); + assert_eq!(layout.scalar_product_r2(), 117); + assert_eq!(layout.scalar_product_r3(), 118); + assert_eq!(DORY_REDUCE_ROUNDS_START, 119); + } + + #[test] + fn reduce_round_ranges_are_contiguous() { + let round = DoryReduceRoundArtifactRanges::new(DORY_REDUCE_ROUNDS_START); + + assert_eq!(round.first_d1_left(), 119..135); + assert_eq!(round.first_d1_right(), 135..151); + assert_eq!(round.first_d2_left(), 151..167); + assert_eq!(round.first_d2_right(), 167..183); + assert_eq!(round.first_e1_beta(), 183..186); + assert_eq!(round.first_e2_beta(), 186..191); + assert_eq!(round.second_c_plus(), 191..207); + assert_eq!(round.second_c_minus(), 207..223); + assert_eq!(round.second_e1_plus(), 223..226); + assert_eq!(round.second_e1_minus(), 226..229); + assert_eq!(round.second_e2_plus(), 229..234); + assert_eq!(round.second_e2_minus(), 234..239); + assert_eq!(round.full().len(), REDUCE_ROUND_ARTIFACT_COORDS); + } + + #[test] + fn expected_len_accounts_for_reduce_rounds_and_final_pair() { + assert_eq!( + DoryProofArtifactLayout::new(0).expected_len(), + DORY_REDUCE_ROUNDS_START + G1_ARTIFACT_COORDS + G2_ARTIFACT_COORDS + ); + assert_eq!( + DoryProofArtifactLayout::new(2).expected_len(), + DORY_REDUCE_ROUNDS_START + + 2 * REDUCE_ROUND_ARTIFACT_COORDS + + G1_ARTIFACT_COORDS + + G2_ARTIFACT_COORDS + ); + } + + #[test] + fn copy_artifact_rejects_wrong_width() { + let artifacts = vec![Fq::default(); GT_ARTIFACT_COEFFS]; + + assert!( + copy_artifact::<{ GT_ARTIFACT_COEFFS }>(&artifacts, 0..GT_ARTIFACT_COEFFS).is_some() + ); + assert!( + copy_artifact::<{ G1_ARTIFACT_COORDS }>(&artifacts, 0..GT_ARTIFACT_COEFFS).is_none() + ); + } +} diff --git a/crates/jolt-dory-assist-verifier/src/config.rs b/crates/jolt-dory-assist-verifier/src/config.rs new file mode 100644 index 0000000000..290a1d6264 --- /dev/null +++ b/crates/jolt-dory-assist-verifier/src/config.rs @@ -0,0 +1,6 @@ +//! Dory-assist verifier configuration. + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistConfig; diff --git a/crates/jolt-dory-assist-verifier/src/error.rs b/crates/jolt-dory-assist-verifier/src/error.rs new file mode 100644 index 0000000000..7455e9a131 --- /dev/null +++ b/crates/jolt-dory-assist-verifier/src/error.rs @@ -0,0 +1,113 @@ +//! Dory-assist verifier errors. + +use std::fmt::{Display, Formatter}; + +use jolt_claims::protocols::dory_assist::{ + DoryAssistChallengeId, DoryAssistOpeningId, DoryAssistPublicId, DoryAssistRelationId, +}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum DoryAssistStage { + CheckedInputs, + Stage1, + Stage2, + Stage3, + HyraxOpening, + NativeOutput, +} + +impl Display for DoryAssistStage { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Self::CheckedInputs => "checked inputs", + Self::Stage1 => "stage 1", + Self::Stage2 => "stage 2", + Self::Stage3 => "stage 3", + Self::HyraxOpening => "Hyrax opening", + Self::NativeOutput => "native output", + }) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)] +pub enum DoryAssistVerifierError { + #[error("invalid Dory-assist verifier mode: expected {expected}, got {got}")] + InvalidMode { + expected: &'static str, + got: &'static str, + }, + + #[error("invalid Dory-assist proof shape in {component}: {reason}")] + InvalidProofShape { + component: &'static str, + reason: String, + }, + + #[error("Dory-assist checked input mismatch: {reason}")] + CheckedInputMismatch { reason: String }, + + #[error("Dory-assist {stage} claim mismatch: {reason}")] + StageClaimMismatch { + stage: DoryAssistStage, + reason: String, + }, + + #[error("missing Dory-assist opening claim for {id:?}")] + MissingOpeningClaim { id: DoryAssistOpeningId }, + + #[error("missing Dory-assist stage challenge for {id:?}")] + MissingStageClaimChallenge { id: DoryAssistChallengeId }, + + #[error("missing Dory-assist public claim for {id:?}")] + MissingStageClaimPublic { id: DoryAssistPublicId }, + + #[error("Dory-assist {stage} sumcheck failed for {relation:?}: {reason}")] + StageSumcheckFailed { + stage: DoryAssistStage, + relation: DoryAssistRelationId, + reason: String, + }, + + #[error("Dory-assist {stage} output mismatch: {reason}")] + StageOutputMismatch { + stage: DoryAssistStage, + reason: String, + }, + + #[error("Dory-assist opening claim mismatch: {reason}")] + OpeningClaimMismatch { reason: String }, + + #[error("Dory-assist Hyrax opening verification failed: {0}")] + HyraxOpeningFailed(#[from] jolt_hyrax::HyraxError), + + #[error("Dory-assist public output mismatch: {reason}")] + PublicOutputMismatch { reason: String }, + + #[error("Dory-assist transcript mismatch: {reason}")] + TranscriptMismatch { reason: String }, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn stage_display_names_are_stable() { + assert_eq!(DoryAssistStage::CheckedInputs.to_string(), "checked inputs"); + assert_eq!(DoryAssistStage::Stage1.to_string(), "stage 1"); + assert_eq!(DoryAssistStage::Stage2.to_string(), "stage 2"); + assert_eq!(DoryAssistStage::Stage3.to_string(), "stage 3"); + assert_eq!(DoryAssistStage::HyraxOpening.to_string(), "Hyrax opening"); + assert_eq!(DoryAssistStage::NativeOutput.to_string(), "native output"); + } + + #[test] + fn hyrax_error_conversion_preserves_source() { + let error = DoryAssistVerifierError::from(jolt_hyrax::HyraxError::EvaluationMismatch); + + assert_eq!( + error, + DoryAssistVerifierError::HyraxOpeningFailed(jolt_hyrax::HyraxError::EvaluationMismatch) + ); + } +} diff --git a/crates/jolt-dory-assist-verifier/src/lib.rs b/crates/jolt-dory-assist-verifier/src/lib.rs new file mode 100644 index 0000000000..afb7ef779d --- /dev/null +++ b/crates/jolt-dory-assist-verifier/src/lib.rs @@ -0,0 +1,45 @@ +//! Concrete Dory implementation of the generic PCS-assist verifier boundary. + +pub mod artifacts; +pub mod config; +pub mod error; +pub mod native_final; +pub mod proof; +pub mod setup; +pub mod stages; +pub mod verifier; + +pub use config::DoryAssistConfig; +pub use error::{DoryAssistStage, DoryAssistVerifierError}; +pub use proof::{ + DoryAssistDoryReduceClaims, DoryAssistDoryReducePublicClaims, + DoryAssistDoryReduceScalarFoldClaims, DoryAssistG1AdditionClaims, DoryAssistG1Claims, + DoryAssistG1CoordinateClaims, DoryAssistG1PointClaims, DoryAssistG1PublicClaims, + DoryAssistG1ScalarMultiplicationBoundaryClaims, + DoryAssistG1ScalarMultiplicationBoundaryPublicClaims, DoryAssistG1ScalarMultiplicationClaims, + DoryAssistG1ScalarMultiplicationShiftClaims, DoryAssistG2AdditionClaims, DoryAssistG2Claims, + DoryAssistG2CoordinateClaims, DoryAssistG2PointClaims, DoryAssistG2PublicClaims, + DoryAssistG2ScalarMultiplicationBoundaryClaims, + DoryAssistG2ScalarMultiplicationBoundaryPublicClaims, DoryAssistG2ScalarMultiplicationClaims, + DoryAssistG2ScalarMultiplicationShiftClaims, DoryAssistGtExponentiationBasePowerClaims, + DoryAssistGtExponentiationBoundaryClaims, DoryAssistGtExponentiationBoundaryPublicClaims, + DoryAssistGtExponentiationClaims, DoryAssistGtExponentiationDigitBitnessClaims, + DoryAssistGtExponentiationDigitSelectorClaims, DoryAssistGtExponentiationShiftClaims, + DoryAssistGtMultiplicationClaims, DoryAssistGtMultiplicationOpeningClaims, + DoryAssistGtMultiplicationRowClaims, DoryAssistInputPublicClaims, DoryAssistOpeningClaim, + DoryAssistOpeningClaims, DoryAssistProof, DoryAssistProofClaims, DoryAssistPublicOutputs, + DoryAssistStage1Claims, DoryAssistStage1PublicClaims, +}; +pub use setup::{ + derive_hyrax_prover_setup, derive_hyrax_verifier_setup, DoryAssistHyrax, + DoryAssistHyraxProverSetup, DoryAssistHyraxVerifierSetup, DORY_ASSIST_HYRAX_GRUMPKIN_DOMAIN, + DORY_ASSIST_HYRAX_GRUMPKIN_SEED, DORY_ASSIST_HYRAX_GRUMPKIN_SETUP_SEED, +}; +pub use stages::{ + stage1::Stage1Proof as DoryAssistStage1Proof, stage2::Stage2Proof as DoryAssistStage2Proof, + stage3::Stage3Proof as DoryAssistStage3Proof, DoryAssistStageProofs, +}; +pub use verifier::{ + checked_clear_inputs, checked_zk_inputs, verify_clear, verify_zk, CheckedInputs, ClearInputs, + ClearOpeningStatement, DoryAssist, ZkInputs, ZkOpeningStatement, +}; diff --git a/crates/jolt-dory-assist-verifier/src/native_final.rs b/crates/jolt-dory-assist-verifier/src/native_final.rs new file mode 100644 index 0000000000..d6c9874880 --- /dev/null +++ b/crates/jolt-dory-assist-verifier/src/native_final.rs @@ -0,0 +1,652 @@ +use jolt_crypto::{Bn254, Bn254Fq12, Bn254G1, Bn254G2, Bn254GT, JoltGroup}; +use jolt_dory::{DoryReduceRoundArtifacts, DoryVerifierTranscriptScalars}; +use jolt_field::{CanonicalBytes, FixedByteSize, Fq, Fr, FromPrimitiveInt}; + +use crate::{ + artifacts::{G1_ARTIFACT_COORDS, G2_ARTIFACT_COORDS, GT_ARTIFACT_COEFFS}, + proof::{ + NATIVE_FINAL_D1_START, NATIVE_FINAL_D2_INIT_START, NATIVE_FINAL_D2_START, + NATIVE_FINAL_E1_INIT_START, NATIVE_FINAL_E1_START, NATIVE_FINAL_E2_START, + NATIVE_FINAL_GT_C_START, NATIVE_FINAL_INPUT_LEN, NATIVE_FINAL_S1_ACC_INDEX, + NATIVE_FINAL_S2_ACC_INDEX, + }, + verifier::{inject_fr_to_fq, ClearOpeningStatement, ZkOpeningStatement}, + DoryAssistVerifierError, +}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct NativeFinalPairingCheck { + pub g1_terms: [Bn254G1; 4], + pub g2_terms: [Bn254G2; 4], + pub rhs: Bn254GT, +} + +impl NativeFinalPairingCheck { + pub fn pre_final_exponentiation(&self) -> Bn254Fq12 { + Bn254::multi_miller_loop(&self.g1_terms, &self.g2_terms) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ZkNativeFinalPairingCheck { + pub g1_term: Bn254G1, + pub g2_term: Bn254G2, + pub rhs: Bn254GT, +} + +impl ZkNativeFinalPairingCheck { + pub fn pre_final_exponentiation(&self) -> Bn254Fq12 { + Bn254::multi_miller_loop(&[self.g1_term], &[self.g2_term]) + } +} + +pub fn transparent_final_pairing_check( + input: &ClearOpeningStatement<'_>, + scalars: &DoryVerifierTranscriptScalars, + native_final_inputs: &[Fq], +) -> Result { + let setup = input.setup.artifacts(); + let proof = input.pcs_proof; + let final_artifacts = proof.final_artifacts(); + let state = native_final_input_state(native_final_inputs)?; + + transparent_final_pairing_check_from_state(&setup, &final_artifacts, &state, scalars) +} + +pub fn transparent_replayed_final_pairing_check( + input: &ClearOpeningStatement<'_>, + scalars: &DoryVerifierTranscriptScalars, +) -> Result { + let setup = input.setup.artifacts(); + let final_artifacts = input.pcs_proof.final_artifacts(); + let state = replay_reduce_native_state( + input.pcs_proof, + &setup, + &input.commitment.0, + setup.g2_0.scalar_mul(&input.eval), + scalars, + )?; + + transparent_final_pairing_check_from_state(&setup, &final_artifacts, &state, scalars) +} + +fn transparent_final_pairing_check_from_state( + setup: &jolt_dory::DoryVerifierSetupArtifacts, + final_artifacts: &jolt_dory::DoryFinalArtifacts, + state: &DoryReduceNativeState, + scalars: &DoryVerifierTranscriptScalars, +) -> Result { + let d = scalars.d; + let d_inverse = scalars.d_inverse; + let d_squared = scalars.d_squared; + let gamma = scalars.gamma; + let gamma_inverse = scalars.gamma_inverse; + let s_product = state.s1_acc * state.s2_acc; + let chi_0 = *setup.chi.first().ok_or_else(|| { + invalid_final_shape("verifier setup chi must contain the final chi_0 term") + })?; + + let rhs = state.c + + setup.ht.scalar_mul(&s_product) + + chi_0 + + state.d2.scalar_mul(&d) + + state.d1.scalar_mul(&d_inverse) + + state.d2_init.scalar_mul(&d_squared); + + Ok(NativeFinalPairingCheck { + g1_terms: [ + final_artifacts.e1 + setup.g1_0.scalar_mul(&d), + setup.h1, + (state.e1 + setup.g1_0.scalar_mul(&(d * state.s2_acc))).scalar_mul(&(-gamma_inverse)), + state.e1_init.scalar_mul(&d_squared), + ], + g2_terms: [ + final_artifacts.e2 + setup.g2_0.scalar_mul(&d_inverse), + (state.e2 + setup.g2_0.scalar_mul(&(d_inverse * state.s1_acc))).scalar_mul(&(-gamma)), + setup.h2, + setup.g2_0, + ], + rhs, + }) +} + +pub fn zk_final_pairing_check( + input: &ZkOpeningStatement<'_>, + scalars: &DoryVerifierTranscriptScalars, + native_final_inputs: &[Fq], +) -> Result { + let setup = input.setup.artifacts(); + let proof = input.pcs_proof; + let scalar_product = proof.scalar_product_artifacts().ok_or_else(|| { + invalid_final_shape("ZK final check missing Dory scalar-product proof artifacts") + })?; + let sigma_c = scalars + .scalar_product_sigma_c + .ok_or_else(|| invalid_final_shape("ZK final check missing sigma_c transcript scalar"))?; + let state = native_final_input_state(native_final_inputs)?; + + zk_final_pairing_check_from_state(&setup, &scalar_product, &state, scalars, sigma_c) +} + +pub fn zk_replayed_final_pairing_check( + input: &ZkOpeningStatement<'_>, + scalars: &DoryVerifierTranscriptScalars, +) -> Result { + let setup = input.setup.artifacts(); + let proof = input.pcs_proof; + let e2 = proof + .zk_artifacts() + .e2 + .ok_or_else(|| invalid_final_shape("ZK final check missing Dory proof e2 artifact"))?; + let scalar_product = proof.scalar_product_artifacts().ok_or_else(|| { + invalid_final_shape("ZK final check missing Dory scalar-product proof artifacts") + })?; + let sigma_c = scalars + .scalar_product_sigma_c + .ok_or_else(|| invalid_final_shape("ZK final check missing sigma_c transcript scalar"))?; + let state = replay_reduce_native_state(proof, &setup, &input.commitment.0, e2, scalars)?; + + zk_final_pairing_check_from_state(&setup, &scalar_product, &state, scalars, sigma_c) +} + +fn zk_final_pairing_check_from_state( + setup: &jolt_dory::DoryVerifierSetupArtifacts, + scalar_product: &jolt_dory::DoryScalarProductProofArtifacts, + state: &DoryReduceNativeState, + scalars: &DoryVerifierTranscriptScalars, + sigma_c: Fr, +) -> Result { + let d = scalars.d; + let d_inverse = scalars.d_inverse; + let sigma_c_squared = sigma_c * sigma_c; + let chi_0 = *setup.chi.first().ok_or_else(|| { + invalid_final_shape("verifier setup chi must contain the final chi_0 term") + })?; + let ht_scalar = scalar_product.r3 + d * scalar_product.r2 + d_inverse * scalar_product.r1; + + let rhs = chi_0 + + scalar_product.r + + scalar_product.q.scalar_mul(&sigma_c) + + state.c.scalar_mul(&sigma_c_squared) + + scalar_product.p2.scalar_mul(&d) + + state.d2.scalar_mul(&(d * sigma_c)) + + scalar_product.p1.scalar_mul(&d_inverse) + + state.d1.scalar_mul(&(d_inverse * sigma_c)) + - setup.ht.scalar_mul(&ht_scalar); + + Ok(ZkNativeFinalPairingCheck { + g1_term: scalar_product.e1 + setup.g1_0.scalar_mul(&d), + g2_term: scalar_product.e2 + setup.g2_0.scalar_mul(&d_inverse), + rhs, + }) +} + +pub fn transparent_native_final_input_claims( + input: &ClearOpeningStatement<'_>, + scalars: &DoryVerifierTranscriptScalars, +) -> Result, DoryAssistVerifierError> { + let setup = input.setup.artifacts(); + let state = replay_reduce_native_state( + input.pcs_proof, + &setup, + &input.commitment.0, + setup.g2_0.scalar_mul(&input.eval), + scalars, + )?; + + Ok(native_final_input_claims(&state)) +} + +pub fn zk_native_final_input_claims( + input: &ZkOpeningStatement<'_>, + scalars: &DoryVerifierTranscriptScalars, +) -> Result, DoryAssistVerifierError> { + let setup = input.setup.artifacts(); + let e2 = input + .pcs_proof + .zk_artifacts() + .e2 + .ok_or_else(|| invalid_final_shape("ZK final inputs missing Dory proof e2 artifact"))?; + let state = + replay_reduce_native_state(input.pcs_proof, &setup, &input.commitment.0, e2, scalars)?; + + Ok(native_final_input_claims(&state)) +} + +fn replay_reduce_native_state( + proof: &jolt_dory::DoryProof, + setup: &jolt_dory::DoryVerifierSetupArtifacts, + commitment: &Bn254GT, + e2: Bn254G2, + scalars: &DoryVerifierTranscriptScalars, +) -> Result { + let vmv = proof.vmv_artifacts(); + let mut state = DoryReduceNativeState { + c: vmv.c, + d1: *commitment, + d2: vmv.d2, + e1: vmv.e1, + e2, + e1_init: vmv.e1, + d2_init: vmv.d2, + s1_acc: Fr::from_u64(1), + s2_acc: Fr::from_u64(1), + }; + + let round_artifacts = proof.reduce_round_artifacts(); + if round_artifacts.len() != scalars.reduce_rounds.len() { + return Err(DoryAssistVerifierError::InvalidProofShape { + component: "dory_final.reduce_rounds", + reason: format!( + "Dory final native replay has {} reduce artifacts but {} scalar rounds", + round_artifacts.len(), + scalars.reduce_rounds.len() + ), + }); + } + + for (round, (artifacts, scalar_round)) in round_artifacts + .iter() + .zip(&scalars.reduce_rounds) + .enumerate() + { + let setup_index = round_artifacts.len() - round; + state.process_round( + artifacts, + setup, + setup_index, + DoryReduceNativeScalars { + beta: scalar_round.beta, + beta_inverse: scalar_round.beta_inverse, + alpha: scalar_round.alpha, + alpha_inverse: scalar_round.alpha_inverse, + alpha_beta: scalar_round.alpha_beta, + alpha_inverse_beta_inverse: scalar_round.alpha_inverse_beta_inverse, + s1_fold_factor: scalar_round.s1_fold_factor, + s2_fold_factor: scalar_round.s2_fold_factor, + }, + )?; + } + + Ok(state) +} + +fn native_final_input_claims(state: &DoryReduceNativeState) -> Vec { + let mut claims = vec![Fq::default(); NATIVE_FINAL_INPUT_LEN]; + claims[NATIVE_FINAL_GT_C_START..NATIVE_FINAL_GT_C_START + GT_ARTIFACT_COEFFS] + .copy_from_slice(>_artifact_coefficients(&state.c)); + claims[NATIVE_FINAL_D1_START..NATIVE_FINAL_D1_START + GT_ARTIFACT_COEFFS] + .copy_from_slice(>_artifact_coefficients(&state.d1)); + claims[NATIVE_FINAL_D2_START..NATIVE_FINAL_D2_START + GT_ARTIFACT_COEFFS] + .copy_from_slice(>_artifact_coefficients(&state.d2)); + claims[NATIVE_FINAL_E1_START..NATIVE_FINAL_E1_START + G1_ARTIFACT_COORDS] + .copy_from_slice(&g1_artifact_coordinates(state.e1)); + claims[NATIVE_FINAL_E2_START..NATIVE_FINAL_E2_START + G2_ARTIFACT_COORDS] + .copy_from_slice(&g2_artifact_coordinates(state.e2)); + claims[NATIVE_FINAL_E1_INIT_START..NATIVE_FINAL_E1_INIT_START + G1_ARTIFACT_COORDS] + .copy_from_slice(&g1_artifact_coordinates(state.e1_init)); + claims[NATIVE_FINAL_D2_INIT_START..NATIVE_FINAL_D2_INIT_START + GT_ARTIFACT_COEFFS] + .copy_from_slice(>_artifact_coefficients(&state.d2_init)); + claims[NATIVE_FINAL_S1_ACC_INDEX] = inject_fr_to_fq(state.s1_acc); + claims[NATIVE_FINAL_S2_ACC_INDEX] = inject_fr_to_fq(state.s2_acc); + claims +} + +fn native_final_input_state( + native_final_inputs: &[Fq], +) -> Result { + let actual_len = native_final_inputs.len(); + if actual_len != NATIVE_FINAL_INPUT_LEN { + return Err(DoryAssistVerifierError::InvalidProofShape { + component: "claims.stage1.public.native_final.inputs", + reason: format!( + "native final input claim vector has length {actual_len}, expected {NATIVE_FINAL_INPUT_LEN}" + ), + }); + } + + Ok(DoryReduceNativeState { + c: decode_native_final_gt(native_final_inputs, NATIVE_FINAL_GT_C_START, "C_acc")?, + d1: decode_native_final_gt(native_final_inputs, NATIVE_FINAL_D1_START, "D1_acc")?, + d2: decode_native_final_gt(native_final_inputs, NATIVE_FINAL_D2_START, "D2_acc")?, + e1: decode_native_final_g1(native_final_inputs, NATIVE_FINAL_E1_START, "E1_acc")?, + e2: decode_native_final_g2(native_final_inputs, NATIVE_FINAL_E2_START, "E2_acc")?, + e1_init: decode_native_final_g1( + native_final_inputs, + NATIVE_FINAL_E1_INIT_START, + "E1_init", + )?, + d2_init: decode_native_final_gt( + native_final_inputs, + NATIVE_FINAL_D2_INIT_START, + "D2_init", + )?, + s1_acc: decode_native_final_fr(native_final_inputs, NATIVE_FINAL_S1_ACC_INDEX, "s1_acc")?, + s2_acc: decode_native_final_fr(native_final_inputs, NATIVE_FINAL_S2_ACC_INDEX, "s2_acc")?, + }) +} + +fn decode_native_final_gt( + inputs: &[Fq], + start: usize, + label: &'static str, +) -> Result { + let mut coefficients = [Fq::default(); Bn254GT::FQ12_COEFFICIENTS]; + coefficients.copy_from_slice(&inputs[start..start + Bn254GT::FQ12_COEFFICIENTS]); + + for (offset, value) in inputs[start + Bn254GT::FQ12_COEFFICIENTS..start + GT_ARTIFACT_COEFFS] + .iter() + .enumerate() + { + if *value != Fq::default() { + return Err(invalid_final_shape(format!( + "{label} GT padding slot {} must be zero", + Bn254GT::FQ12_COEFFICIENTS + offset + ))); + } + } + + Bn254GT::from_fq12_coefficients(coefficients) + .ok_or_else(|| invalid_final_shape(format!("{label} is not a valid BN254 GT element"))) +} + +fn decode_native_final_g1( + inputs: &[Fq], + start: usize, + label: &'static str, +) -> Result { + let mut coordinates = [Fq::default(); G1_ARTIFACT_COORDS]; + coordinates.copy_from_slice(&inputs[start..start + G1_ARTIFACT_COORDS]); + + Bn254G1::from_affine_coordinates_with_infinity(coordinates) + .ok_or_else(|| invalid_final_shape(format!("{label} is not a valid BN254 G1 point"))) +} + +fn decode_native_final_g2( + inputs: &[Fq], + start: usize, + label: &'static str, +) -> Result { + let mut coordinates = [Fq::default(); G2_ARTIFACT_COORDS]; + coordinates.copy_from_slice(&inputs[start..start + G2_ARTIFACT_COORDS]); + + Bn254G2::from_affine_coordinates_with_infinity(coordinates) + .ok_or_else(|| invalid_final_shape(format!("{label} is not a valid BN254 G2 point"))) +} + +fn decode_native_final_fr( + inputs: &[Fq], + index: usize, + label: &'static str, +) -> Result { + let value = inputs[index]; + let mut bytes = [0_u8; Fq::NUM_BYTES]; + value.to_bytes_le(&mut bytes); + let scalar = Fr::from_le_bytes_mod_order(&bytes); + if inject_fr_to_fq(scalar) != value { + return Err(invalid_final_shape(format!( + "{label} is not a canonical injected BN254 Fr scalar" + ))); + } + + Ok(scalar) +} + +fn gt_artifact_coefficients(value: &Bn254GT) -> [Fq; GT_ARTIFACT_COEFFS] { + let mut coefficients = [Fq::default(); GT_ARTIFACT_COEFFS]; + coefficients[..Bn254GT::FQ12_COEFFICIENTS].copy_from_slice(&value.fq12_coefficients()); + coefficients +} + +fn g1_artifact_coordinates(value: Bn254G1) -> [Fq; G1_ARTIFACT_COORDS] { + value.affine_coordinates_with_infinity() +} + +fn g2_artifact_coordinates(value: Bn254G2) -> [Fq; G2_ARTIFACT_COORDS] { + value.affine_coordinates_with_infinity() +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct DoryReduceNativeState { + c: Bn254GT, + d1: Bn254GT, + d2: Bn254GT, + e1: Bn254G1, + e2: Bn254G2, + e1_init: Bn254G1, + d2_init: Bn254GT, + s1_acc: Fr, + s2_acc: Fr, +} + +impl DoryReduceNativeState { + fn process_round( + &mut self, + artifacts: &DoryReduceRoundArtifacts, + setup: &jolt_dory::DoryVerifierSetupArtifacts, + setup_index: usize, + scalars: DoryReduceNativeScalars, + ) -> Result<(), DoryAssistVerifierError> { + self.c = self.c + + setup_gt(&setup.chi, setup_index, "chi")? + + self.d2.scalar_mul(&scalars.beta) + + self.d1.scalar_mul(&scalars.beta_inverse) + + artifacts.second.c_plus.scalar_mul(&scalars.alpha) + + artifacts.second.c_minus.scalar_mul(&scalars.alpha_inverse); + + self.d1 = artifacts.first.d1_left.scalar_mul(&scalars.alpha) + + artifacts.first.d1_right + + setup_gt(&setup.delta_1l, setup_index, "delta_1l")?.scalar_mul(&scalars.alpha_beta) + + setup_gt(&setup.delta_1r, setup_index, "delta_1r")?.scalar_mul(&scalars.beta); + + self.d2 = artifacts.first.d2_left.scalar_mul(&scalars.alpha_inverse) + + artifacts.first.d2_right + + setup_gt(&setup.delta_2l, setup_index, "delta_2l")? + .scalar_mul(&scalars.alpha_inverse_beta_inverse) + + setup_gt(&setup.delta_2r, setup_index, "delta_2r")?.scalar_mul(&scalars.beta_inverse); + + self.e1 = self.e1 + + artifacts.first.e1_beta.scalar_mul(&scalars.beta) + + artifacts.second.e1_plus.scalar_mul(&scalars.alpha) + + artifacts.second.e1_minus.scalar_mul(&scalars.alpha_inverse); + + self.e2 = self.e2 + + artifacts.first.e2_beta.scalar_mul(&scalars.beta_inverse) + + artifacts.second.e2_plus.scalar_mul(&scalars.alpha) + + artifacts.second.e2_minus.scalar_mul(&scalars.alpha_inverse); + + self.s1_acc *= scalars.s1_fold_factor; + self.s2_acc *= scalars.s2_fold_factor; + Ok(()) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct DoryReduceNativeScalars { + beta: Fr, + beta_inverse: Fr, + alpha: Fr, + alpha_inverse: Fr, + alpha_beta: Fr, + alpha_inverse_beta_inverse: Fr, + s1_fold_factor: Fr, + s2_fold_factor: Fr, +} + +fn setup_gt( + values: &[Bn254GT], + index: usize, + name: &'static str, +) -> Result { + values + .get(index) + .copied() + .ok_or_else(|| invalid_final_shape(format!("verifier setup {name} missing index {index}"))) +} + +fn invalid_final_shape(reason: impl Into) -> DoryAssistVerifierError { + DoryAssistVerifierError::InvalidProofShape { + component: "dory_final", + reason: reason.into(), + } +} + +#[cfg(test)] +#[expect(clippy::expect_used, reason = "tests fail loudly on invalid fixtures")] +mod tests { + use jolt_crypto::PairingGroup; + use jolt_dory::{DoryScheme, DoryVerifierSetup}; + use jolt_field::{Fr, FromPrimitiveInt}; + use jolt_openings::{CommitmentScheme, ZkOpeningScheme}; + use jolt_poly::Polynomial; + use jolt_transcript::{Blake2bTranscript, Transcript}; + + use super::*; + + #[test] + fn transparent_final_pairing_check_matches_dory_verifier_equation() { + let fixture = final_check_fixture(); + let scalars = fixture + .proof + .verifier_transcript_scalars(&fixture.verifier_transcript, &fixture.point); + let native_final_inputs = + transparent_native_final_input_claims(&fixture.clear_statement(), &scalars) + .expect("transparent native-final inputs are well shaped"); + let check = transparent_final_pairing_check( + &fixture.clear_statement(), + &scalars, + &native_final_inputs, + ) + .expect("transparent final check is well shaped"); + let replayed = + transparent_replayed_final_pairing_check(&fixture.clear_statement(), &scalars) + .expect("transparent replayed final check is well shaped"); + + assert_eq!(check, replayed); + assert_eq!( + check.pre_final_exponentiation().final_exponentiation(), + Some(check.rhs) + ); + assert_eq!( + Bn254::multi_pairing(&check.g1_terms, &check.g2_terms), + check.rhs + ); + } + + #[test] + fn zk_final_pairing_check_matches_dory_verifier_equation() { + let fixture = zk_final_check_fixture(); + let scalars = fixture + .proof + .verifier_transcript_scalars(&fixture.verifier_transcript, &fixture.point); + let native_final_inputs = zk_native_final_input_claims(&fixture.zk_statement(), &scalars) + .expect("ZK native-final inputs are well shaped"); + let check = zk_final_pairing_check(&fixture.zk_statement(), &scalars, &native_final_inputs) + .expect("ZK final check"); + let replayed = zk_replayed_final_pairing_check(&fixture.zk_statement(), &scalars) + .expect("ZK replayed final check"); + + assert!(scalars.scalar_product_sigma_c.is_some()); + assert_eq!(check, replayed); + assert_eq!( + check.pre_final_exponentiation().final_exponentiation(), + Some(check.rhs) + ); + assert_eq!(Bn254::pairing(&check.g1_term, &check.g2_term), check.rhs); + } + + struct FinalCheckFixture { + verifier_setup: DoryVerifierSetup, + proof: jolt_dory::DoryProof, + commitment: jolt_dory::DoryCommitment, + point: Vec, + eval: Fr, + verifier_transcript: Blake2bTranscript, + } + + impl FinalCheckFixture { + fn clear_statement(&self) -> ClearOpeningStatement<'_> { + ClearOpeningStatement { + setup: &self.verifier_setup, + pcs_proof: &self.proof, + commitment: &self.commitment, + point: &self.point, + eval: self.eval, + } + } + + fn zk_statement(&self) -> ZkOpeningStatement<'_> { + ZkOpeningStatement { + setup: &self.verifier_setup, + pcs_proof: &self.proof, + commitment: &self.commitment, + point: &self.point, + } + } + } + + fn final_check_fixture() -> FinalCheckFixture { + let (prover_setup, verifier_setup) = DoryScheme::setup(2); + let poly = Polynomial::::from(vec![ + Fr::from_u64(1), + Fr::from_u64(2), + Fr::from_u64(3), + Fr::from_u64(4), + ]); + let point = vec![Fr::from_u64(5), Fr::from_u64(7)]; + let eval = poly.evaluate(&point); + let (commitment, hint) = DoryScheme::commit(poly.evaluations(), &prover_setup); + let mut prover_transcript = Blake2bTranscript::new(b"dory-assist-native-final-fixture"); + let proof = DoryScheme::open( + &poly, + &point, + eval, + &prover_setup, + Some(hint), + &mut prover_transcript, + ); + let verifier_transcript = Blake2bTranscript::new(b"dory-assist-native-final-fixture"); + + FinalCheckFixture { + verifier_setup, + proof, + commitment, + point, + eval, + verifier_transcript, + } + } + + fn zk_final_check_fixture() -> FinalCheckFixture { + let (prover_setup, verifier_setup) = DoryScheme::setup(2); + let poly = Polynomial::::from(vec![ + Fr::from_u64(1), + Fr::from_u64(2), + Fr::from_u64(3), + Fr::from_u64(4), + ]); + let point = vec![Fr::from_u64(5), Fr::from_u64(7)]; + let eval = poly.evaluate(&point); + let (commitment, hint) = + ::commit_zk(poly.evaluations(), &prover_setup); + let mut prover_transcript = Blake2bTranscript::new(b"dory-assist-native-final-zk"); + let (proof, _hiding_commitment, _blind) = DoryScheme::open_zk( + &poly, + &point, + eval, + &prover_setup, + hint, + &mut prover_transcript, + ); + let verifier_transcript = Blake2bTranscript::new(b"dory-assist-native-final-zk"); + + FinalCheckFixture { + verifier_setup, + proof, + commitment, + point, + eval, + verifier_transcript, + } + } +} diff --git a/crates/jolt-dory-assist-verifier/src/proof.rs b/crates/jolt-dory-assist-verifier/src/proof.rs new file mode 100644 index 0000000000..f21da095eb --- /dev/null +++ b/crates/jolt-dory-assist-verifier/src/proof.rs @@ -0,0 +1,1426 @@ +//! Dory-assist proof payload types. + +use jolt_claims::protocols::dory_assist::{ + formulas::{composition, dory_reduce, g1, g2, gt, miller_loop}, + DoryAssistBoundaryEndpoint, DoryAssistDimensions, DoryAssistOpeningId, DoryAssistPolynomialId, + DoryAssistPublicId, DoryAssistRelationId, DoryAssistVirtualPolynomial, DoryReduceDimensions, + DoryReducePolynomial, G1Dimensions, G1Polynomial, G2Dimensions, G2Polynomial, GtDimensions, + GtPolynomial, MillerLoopConstant, MillerLoopDimensions, MillerLoopPolynomial, + MillerLoopSelector, PrefixPackingDimensions, WiringDimensions, +}; +use jolt_crypto::{Bn254Fq12, GrumpkinPoint}; +use jolt_field::{Fq, FromPrimitiveInt, Invertible}; +use jolt_hyrax::{HyraxCommitment, HyraxOpeningProof}; +use serde::{Deserialize, Serialize}; + +use crate::stages::DoryAssistStageProofs; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistProof { + pub dimensions: DoryAssistDimensions, + pub stages: DoryAssistStageProofs, + pub opening_proof: HyraxOpeningProof, + pub claims: DoryAssistProofClaims, + pub dense_commitment: HyraxCommitment, + pub public_outputs: DoryAssistPublicOutputs, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistPublicOutputs { + pub pre_final_exponentiation: Bn254Fq12, +} + +impl DoryAssistPublicOutputs { + pub fn pre_final_exponentiation_coefficients( + &self, + ) -> [Fq; miller_loop::MILLER_LOOP_GT_COEFFS] { + let mut coefficients = [Fq::default(); miller_loop::MILLER_LOOP_GT_COEFFS]; + coefficients[..Bn254Fq12::COEFFICIENTS] + .copy_from_slice(&self.pre_final_exponentiation.coefficients()); + coefficients + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistProofClaims { + pub stage1: DoryAssistStage1Claims, + pub opening: DoryAssistOpeningClaims, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistStage1Claims { + pub public: DoryAssistStage1PublicClaims, + pub gt_exponentiation: DoryAssistGtExponentiationClaims, + pub gt_exponentiation_digit_selector: DoryAssistGtExponentiationDigitSelectorClaims, + pub gt_exponentiation_base_power: DoryAssistGtExponentiationBasePowerClaims, + pub gt_exponentiation_digit_bitness: DoryAssistGtExponentiationDigitBitnessClaims, + pub gt_exponentiation_shift: DoryAssistGtExponentiationShiftClaims, + pub gt_exponentiation_boundary: DoryAssistGtExponentiationBoundaryClaims, + pub gt_multiplication: DoryAssistGtMultiplicationClaims, + pub g1: DoryAssistG1Claims, + pub g2: DoryAssistG2Claims, + pub miller_loop: DoryAssistMillerLoopClaims, + pub dory_reduce: DoryAssistDoryReduceClaims, +} + +impl DoryAssistStage1Claims { + pub fn opening_claim(&self, id: &DoryAssistOpeningId) -> Option { + self.gt_exponentiation + .opening_claim(id) + .or_else(|| self.gt_exponentiation_digit_selector.opening_claim(id)) + .or_else(|| self.gt_exponentiation_base_power.opening_claim(id)) + .or_else(|| self.gt_exponentiation_digit_bitness.opening_claim(id)) + .or_else(|| self.gt_exponentiation_shift.opening_claim(id)) + .or_else(|| self.gt_exponentiation_boundary.opening_claim(id)) + .or_else(|| self.gt_multiplication.opening_claim(id)) + .or_else(|| self.g1.opening_claim(id)) + .or_else(|| self.g2.opening_claim(id)) + .or_else(|| self.miller_loop.opening_claim(id)) + .or_else(|| self.dory_reduce.opening_claim(id)) + } + + pub fn public_claim(&self, id: &DoryAssistPublicId) -> Option { + self.public.claim(id) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistStage1PublicClaims { + pub input: DoryAssistInputPublicClaims, + pub gt_shift_eq_kernel: Fq, + pub dory_reduce_shift_eq_kernel: Fq, + pub dory_reduce: DoryAssistDoryReducePublicClaims, + pub native_final: DoryAssistNativeFinalPublicClaims, + pub gt_exponentiation_boundary: DoryAssistGtExponentiationBoundaryPublicClaims, + pub g1: DoryAssistG1PublicClaims, + pub g2: DoryAssistG2PublicClaims, + pub miller_loop: DoryAssistMillerLoopPublicClaims, +} + +impl Default for DoryAssistStage1PublicClaims { + fn default() -> Self { + Self { + input: DoryAssistInputPublicClaims::default(), + gt_shift_eq_kernel: Fq::from_u64(1), + dory_reduce_shift_eq_kernel: Fq::from_u64(1), + dory_reduce: DoryAssistDoryReducePublicClaims::default(), + native_final: DoryAssistNativeFinalPublicClaims::default(), + gt_exponentiation_boundary: DoryAssistGtExponentiationBoundaryPublicClaims::default(), + g1: DoryAssistG1PublicClaims::default(), + g2: DoryAssistG2PublicClaims::default(), + miller_loop: DoryAssistMillerLoopPublicClaims::default(), + } + } +} + +impl DoryAssistStage1PublicClaims { + pub fn claim(&self, id: &DoryAssistPublicId) -> Option { + match *id { + DoryAssistPublicId::GtShiftEqKernel => Some(self.gt_shift_eq_kernel), + DoryAssistPublicId::DoryReduceShiftEqKernel => Some(self.dory_reduce_shift_eq_kernel), + DoryAssistPublicId::NativeFinalCheckInput(index) => self.native_final.claim(index), + _ => self.input.claim(id).or_else(|| { + self.gt_exponentiation_boundary + .claim(id) + .or_else(|| self.dory_reduce.claim(id)) + .or_else(|| self.g1.claim(id)) + .or_else(|| self.g2.claim(id)) + .or_else(|| self.miller_loop.claim(id)) + }), + } + } +} + +pub const NATIVE_FINAL_GT_C_START: usize = dory_reduce::DORY_REDUCE_NATIVE_FINAL_GT_C_START; +pub const NATIVE_FINAL_D1_START: usize = dory_reduce::DORY_REDUCE_NATIVE_FINAL_D1_START; +pub const NATIVE_FINAL_D2_START: usize = dory_reduce::DORY_REDUCE_NATIVE_FINAL_D2_START; +pub const NATIVE_FINAL_E1_START: usize = dory_reduce::DORY_REDUCE_NATIVE_FINAL_E1_START; +pub const NATIVE_FINAL_E2_START: usize = dory_reduce::DORY_REDUCE_NATIVE_FINAL_E2_START; +pub const NATIVE_FINAL_E1_INIT_START: usize = dory_reduce::DORY_REDUCE_NATIVE_FINAL_E1_INIT_START; +pub const NATIVE_FINAL_D2_INIT_START: usize = dory_reduce::DORY_REDUCE_NATIVE_FINAL_D2_INIT_START; +pub const NATIVE_FINAL_S1_ACC_INDEX: usize = dory_reduce::DORY_REDUCE_NATIVE_FINAL_S1_ACC_INDEX; +pub const NATIVE_FINAL_S2_ACC_INDEX: usize = dory_reduce::DORY_REDUCE_NATIVE_FINAL_S2_ACC_INDEX; +pub const NATIVE_FINAL_INPUT_LEN: usize = dory_reduce::DORY_REDUCE_NATIVE_FINAL_INPUT_LEN; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistNativeFinalPublicClaims { + pub inputs: Vec, +} + +impl Default for DoryAssistNativeFinalPublicClaims { + fn default() -> Self { + Self { + inputs: vec![Fq::default(); NATIVE_FINAL_INPUT_LEN], + } + } +} + +impl DoryAssistNativeFinalPublicClaims { + pub fn bind(&mut self, inputs: Vec) { + self.inputs = inputs; + } + + pub fn claim(&self, index: usize) -> Option { + self.inputs.get(index).copied() + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistInputPublicClaims { + pub checked_input_digest: Fq, + pub verifier_setup_digest: Fq, + pub verifier_setup_artifacts: Vec, + pub dory_proof_artifacts: Vec, + pub jolt_commitments: Vec, + pub jolt_evaluation_claims: Vec, + pub dory_reduce_initial_e2: Vec, + pub transcript_scalars: Vec, +} + +impl DoryAssistInputPublicClaims { + pub fn claim(&self, id: &DoryAssistPublicId) -> Option { + match *id { + DoryAssistPublicId::VerifierSetupDigest => Some(self.verifier_setup_digest), + DoryAssistPublicId::VerifierSetupArtifact(index) => { + self.verifier_setup_artifacts.get(index).copied() + } + DoryAssistPublicId::DoryProofArtifact(index) => { + self.dory_proof_artifacts.get(index).copied() + } + DoryAssistPublicId::JoltCommitment(index) => self.jolt_commitments.get(index).copied(), + DoryAssistPublicId::JoltEvaluationClaim(index) => { + self.jolt_evaluation_claims.get(index).copied() + } + DoryAssistPublicId::DoryReduceInitialE2(index) => { + self.dory_reduce_initial_e2.get(index).copied() + } + DoryAssistPublicId::TranscriptScalar(index) => { + self.transcript_scalars.get(index).copied() + } + _ => None, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistDoryReducePublicClaims { + pub boundary_initial_selector: Fq, + pub boundary_final_selector: Fq, +} + +impl Default for DoryAssistDoryReducePublicClaims { + fn default() -> Self { + Self { + boundary_initial_selector: Fq::from_u64(1), + boundary_final_selector: Fq::from_u64(1), + } + } +} + +impl DoryAssistDoryReducePublicClaims { + pub fn claim(&self, id: &DoryAssistPublicId) -> Option { + match *id { + DoryAssistPublicId::BoundarySelector { + relation: DoryAssistRelationId::DoryReduceBoundary, + endpoint: DoryAssistBoundaryEndpoint::Initial, + } => Some(self.boundary_initial_selector), + DoryAssistPublicId::BoundarySelector { + relation: DoryAssistRelationId::DoryReduceBoundary, + endpoint: DoryAssistBoundaryEndpoint::Final, + } => Some(self.boundary_final_selector), + _ => None, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistMillerLoopPublicClaims { + pub line_double_selector: Fq, + pub line_add_selector: Fq, + pub two_inverse: Fq, + pub twist_b: [Fq; 2], + pub pair_product_shift_eq_kernel: Fq, + pub pair_product_initial_selector: Fq, + pub pair_product_final_selector: Fq, + pub pair_product_initial_value: [Fq; miller_loop::MILLER_LOOP_GT_COEFFS], + pub accumulator_shift_eq_kernel: Fq, + pub boundary_initial_selector: Fq, + pub boundary_final_selector: Fq, + pub boundary_initial_value: [Fq; miller_loop::MILLER_LOOP_GT_COEFFS], + pub output_gt: [Fq; miller_loop::MILLER_LOOP_GT_COEFFS], +} + +impl Default for DoryAssistMillerLoopPublicClaims { + fn default() -> Self { + Self { + line_double_selector: Fq::from_u64(1), + line_add_selector: Fq::default(), + two_inverse: Fq::from_u64(2).inverse().unwrap_or_default(), + twist_b: [Fq::default(); 2], + pair_product_shift_eq_kernel: Fq::from_u64(1), + pair_product_initial_selector: Fq::from_u64(1), + pair_product_final_selector: Fq::from_u64(1), + pair_product_initial_value: [Fq::default(); miller_loop::MILLER_LOOP_GT_COEFFS], + accumulator_shift_eq_kernel: Fq::from_u64(1), + boundary_initial_selector: Fq::from_u64(1), + boundary_final_selector: Fq::from_u64(1), + boundary_initial_value: [Fq::default(); miller_loop::MILLER_LOOP_GT_COEFFS], + output_gt: [Fq::default(); miller_loop::MILLER_LOOP_GT_COEFFS], + } + } +} + +impl DoryAssistMillerLoopPublicClaims { + pub fn bind_pre_final_exponentiation(&mut self, outputs: &DoryAssistPublicOutputs) { + self.output_gt = outputs.pre_final_exponentiation_coefficients(); + } + + pub fn claim(&self, id: &DoryAssistPublicId) -> Option { + match *id { + DoryAssistPublicId::MillerLoopSelector { + relation: DoryAssistRelationId::MillerLoopLineStep, + selector: MillerLoopSelector::LineDouble, + } => Some(self.line_double_selector), + DoryAssistPublicId::MillerLoopSelector { + relation: DoryAssistRelationId::MillerLoopLineStep, + selector: MillerLoopSelector::LineAdd, + } => Some(self.line_add_selector), + DoryAssistPublicId::MillerLoopConstant(MillerLoopConstant::TwoInverse) => { + Some(self.two_inverse) + } + DoryAssistPublicId::MillerLoopConstant(MillerLoopConstant::TwistB0) => { + Some(self.twist_b[0]) + } + DoryAssistPublicId::MillerLoopConstant(MillerLoopConstant::TwistB1) => { + Some(self.twist_b[1]) + } + DoryAssistPublicId::MillerLoopShiftEqKernel( + DoryAssistRelationId::MillerLoopPairProduct, + ) => Some(self.pair_product_shift_eq_kernel), + DoryAssistPublicId::MillerLoopShiftEqKernel( + DoryAssistRelationId::MillerLoopAccumulator, + ) => Some(self.accumulator_shift_eq_kernel), + DoryAssistPublicId::BoundarySelector { + relation: DoryAssistRelationId::MillerLoopPairProduct, + endpoint: DoryAssistBoundaryEndpoint::Initial, + } => Some(self.pair_product_initial_selector), + DoryAssistPublicId::BoundarySelector { + relation: DoryAssistRelationId::MillerLoopPairProduct, + endpoint: DoryAssistBoundaryEndpoint::Final, + } => Some(self.pair_product_final_selector), + DoryAssistPublicId::BoundarySelector { + relation: DoryAssistRelationId::MillerLoopBoundary, + endpoint: DoryAssistBoundaryEndpoint::Initial, + } => Some(self.boundary_initial_selector), + DoryAssistPublicId::BoundarySelector { + relation: DoryAssistRelationId::MillerLoopBoundary, + endpoint: DoryAssistBoundaryEndpoint::Final, + } => Some(self.boundary_final_selector), + DoryAssistPublicId::BoundaryValue { + relation: DoryAssistRelationId::MillerLoopPairProduct, + endpoint: DoryAssistBoundaryEndpoint::Initial, + component, + } => self.pair_product_initial_value.get(component).copied(), + DoryAssistPublicId::BoundaryValue { + relation: DoryAssistRelationId::MillerLoopBoundary, + endpoint: DoryAssistBoundaryEndpoint::Initial, + component, + } => self.boundary_initial_value.get(component).copied(), + DoryAssistPublicId::MillerLoopOutputGt(component) => { + self.output_gt.get(component).copied() + } + _ => None, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistGtExponentiationBoundaryPublicClaims { + pub initial_selector: Fq, + pub final_selector: Fq, + pub initial_value: Fq, + pub final_value: Fq, +} + +impl Default for DoryAssistGtExponentiationBoundaryPublicClaims { + fn default() -> Self { + Self { + initial_selector: Fq::from_u64(1), + final_selector: Fq::from_u64(1), + initial_value: Fq::default(), + final_value: Fq::default(), + } + } +} + +impl DoryAssistGtExponentiationBoundaryPublicClaims { + pub fn claim(&self, id: &DoryAssistPublicId) -> Option { + match *id { + DoryAssistPublicId::BoundarySelector { + relation: DoryAssistRelationId::GtExponentiationBoundary, + endpoint: DoryAssistBoundaryEndpoint::Initial, + } => Some(self.initial_selector), + DoryAssistPublicId::BoundarySelector { + relation: DoryAssistRelationId::GtExponentiationBoundary, + endpoint: DoryAssistBoundaryEndpoint::Final, + } => Some(self.final_selector), + DoryAssistPublicId::BoundaryValue { + relation: DoryAssistRelationId::GtExponentiationBoundary, + endpoint: DoryAssistBoundaryEndpoint::Initial, + component: 0, + } => Some(self.initial_value), + DoryAssistPublicId::BoundaryValue { + relation: DoryAssistRelationId::GtExponentiationBoundary, + endpoint: DoryAssistBoundaryEndpoint::Final, + component: 0, + } => Some(self.final_value), + _ => None, + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistGtExponentiationClaims { + pub shifted_accumulator: Fq, + pub accumulator: Fq, + pub digit_selector: Fq, + pub quotient: Fq, + pub modulus: Fq, +} + +impl DoryAssistGtExponentiationClaims { + pub fn opening_claim(&self, id: &DoryAssistOpeningId) -> Option { + match *id { + id if id == gt::exp_shifted_accumulator_opening() => Some(self.shifted_accumulator), + id if id == gt::exp_accumulator_opening() => Some(self.accumulator), + id if id == gt::exp_digit_selector_opening() => Some(self.digit_selector), + id if id == gt::exp_quotient_opening() => Some(self.quotient), + id if id == gt::exp_modulus_opening() => Some(self.modulus), + _ => None, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistGtExponentiationDigitSelectorClaims { + pub digit_selector: Fq, + pub digit_lo: Fq, + pub digit_hi: Fq, + pub base: Fq, + pub base_squared: Fq, + pub base_cubed: Fq, +} + +impl Default for DoryAssistGtExponentiationDigitSelectorClaims { + fn default() -> Self { + Self { + digit_selector: Fq::default(), + digit_lo: Fq::from_u64(1), + digit_hi: Fq::default(), + base: Fq::default(), + base_squared: Fq::default(), + base_cubed: Fq::default(), + } + } +} + +impl DoryAssistGtExponentiationDigitSelectorClaims { + pub fn opening_claim(&self, id: &DoryAssistOpeningId) -> Option { + match *id { + id if id == gt::exp_digit_selector_base_4_opening() => Some(self.digit_selector), + id if id == gt::exp_digit_bit_opening(0) => Some(self.digit_lo), + id if id == gt::exp_digit_bit_opening(1) => Some(self.digit_hi), + id if id == gt::exp_base_power_selector_opening(1) => Some(self.base), + id if id == gt::exp_base_power_selector_opening(2) => Some(self.base_squared), + id if id == gt::exp_base_power_selector_opening(3) => Some(self.base_cubed), + _ => None, + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistGtExponentiationBasePowerClaims { + pub base: Fq, + pub base_squared: Fq, + pub quotient_squared: Fq, + pub modulus: Fq, + pub base_cubed: Fq, + pub quotient_cubed: Fq, +} + +impl DoryAssistGtExponentiationBasePowerClaims { + pub fn opening_claim(&self, id: &DoryAssistOpeningId) -> Option { + match *id { + id if id == gt::exp_base_power_checked_opening(1) => Some(self.base), + id if id == gt::exp_base_power_checked_opening(2) => Some(self.base_squared), + id if id == gt::exp_base_power_quotient_opening(2) => Some(self.quotient_squared), + id if id == gt::exp_base_power_modulus_opening() => Some(self.modulus), + id if id == gt::exp_base_power_checked_opening(3) => Some(self.base_cubed), + id if id == gt::exp_base_power_quotient_opening(3) => Some(self.quotient_cubed), + _ => None, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistGtExponentiationDigitBitnessClaims { + pub digit_lo: Fq, + pub digit_hi: Fq, +} + +impl Default for DoryAssistGtExponentiationDigitBitnessClaims { + fn default() -> Self { + Self { + digit_lo: Fq::from_u64(1), + digit_hi: Fq::default(), + } + } +} + +impl DoryAssistGtExponentiationDigitBitnessClaims { + pub fn opening_claim(&self, id: &DoryAssistOpeningId) -> Option { + match *id { + id if id == gt::exp_digit_bit_bitness_opening(0) => Some(self.digit_lo), + id if id == gt::exp_digit_bit_bitness_opening(1) => Some(self.digit_hi), + _ => None, + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistGtExponentiationShiftClaims { + pub accumulator: Fq, +} + +impl DoryAssistGtExponentiationShiftClaims { + pub fn opening_claim(&self, id: &DoryAssistOpeningId) -> Option { + match *id { + id if id == gt::exp_accumulator_shift_opening() => Some(self.accumulator), + _ => None, + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistGtExponentiationBoundaryClaims { + pub accumulator: Fq, + pub shifted_accumulator: Fq, +} + +impl DoryAssistGtExponentiationBoundaryClaims { + pub fn opening_claim(&self, id: &DoryAssistOpeningId) -> Option { + match *id { + id if id == gt::exp_boundary_accumulator_opening() => Some(self.accumulator), + id if id == gt::exp_boundary_shifted_accumulator_opening() => { + Some(self.shifted_accumulator) + } + _ => None, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistGtMultiplicationClaims { + pub opening: DoryAssistGtMultiplicationOpeningClaims, + pub rows: [DoryAssistGtMultiplicationRowClaims; composition::GT_MULTIPLICATION_ROWS], +} + +impl Default for DoryAssistGtMultiplicationClaims { + fn default() -> Self { + Self { + opening: DoryAssistGtMultiplicationOpeningClaims::default(), + rows: [DoryAssistGtMultiplicationRowClaims::default(); + composition::GT_MULTIPLICATION_ROWS], + } + } +} + +impl DoryAssistGtMultiplicationClaims { + pub fn opening_claim(&self, id: &DoryAssistOpeningId) -> Option { + self.opening + .claim(gt_polynomial(id, DoryAssistRelationId::GtMultiplication)?) + } + + pub fn row_claim(&self, row: usize, component: usize, polynomial: GtPolynomial) -> Option { + self.rows + .get(row) + .and_then(|row| row.claim(component, polynomial)) + } +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistGtMultiplicationOpeningClaims { + pub left: Fq, + pub right: Fq, + pub output: Fq, + pub quotient: Fq, + pub modulus: Fq, +} + +impl DoryAssistGtMultiplicationOpeningClaims { + pub fn claim(&self, polynomial: GtPolynomial) -> Option { + match polynomial { + GtPolynomial::MulLeft => Some(self.left), + GtPolynomial::MulRight => Some(self.right), + GtPolynomial::MulOutput => Some(self.output), + GtPolynomial::MulQuotient => Some(self.quotient), + GtPolynomial::Modulus => Some(self.modulus), + _ => None, + } + } +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistGtMultiplicationRowClaims { + pub left: [Fq; miller_loop::MILLER_LOOP_GT_COEFFS], + pub right: [Fq; miller_loop::MILLER_LOOP_GT_COEFFS], + pub output: [Fq; miller_loop::MILLER_LOOP_GT_COEFFS], + pub quotient: [Fq; miller_loop::MILLER_LOOP_GT_COEFFS], +} + +impl DoryAssistGtMultiplicationRowClaims { + pub fn claim(&self, component: usize, polynomial: GtPolynomial) -> Option { + match polynomial { + GtPolynomial::MulLeft => self.left.get(component).copied(), + GtPolynomial::MulRight => self.right.get(component).copied(), + GtPolynomial::MulOutput => self.output.get(component).copied(), + GtPolynomial::MulQuotient => self.quotient.get(component).copied(), + _ => None, + } + } +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistG1PointClaims { + pub x: Fq, + pub y: Fq, + pub infinity: Fq, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistG1CoordinateClaims { + pub x: Fq, + pub y: Fq, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistG2PointClaims { + pub x: [Fq; 2], + pub y: [Fq; 2], + pub infinity: Fq, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistG2CoordinateClaims { + pub x: [Fq; 2], + pub y: [Fq; 2], +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistG1PublicClaims { + pub scalar_multiplication_boundary: DoryAssistG1ScalarMultiplicationBoundaryPublicClaims, +} + +impl DoryAssistG1PublicClaims { + pub fn claim(&self, id: &DoryAssistPublicId) -> Option { + self.scalar_multiplication_boundary.claim(id) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistG1ScalarMultiplicationBoundaryPublicClaims { + pub initial_selector: Fq, + pub final_selector: Fq, + pub initial_value: DoryAssistG1PointClaims, + pub final_value: DoryAssistG1CoordinateClaims, +} + +impl Default for DoryAssistG1ScalarMultiplicationBoundaryPublicClaims { + fn default() -> Self { + Self { + initial_selector: Fq::from_u64(1), + final_selector: Fq::from_u64(1), + initial_value: DoryAssistG1PointClaims::default(), + final_value: DoryAssistG1CoordinateClaims::default(), + } + } +} + +impl DoryAssistG1ScalarMultiplicationBoundaryPublicClaims { + pub fn claim(&self, id: &DoryAssistPublicId) -> Option { + match *id { + DoryAssistPublicId::BoundarySelector { + relation: DoryAssistRelationId::G1ScalarMultiplicationBoundary, + endpoint: DoryAssistBoundaryEndpoint::Initial, + } => Some(self.initial_selector), + DoryAssistPublicId::BoundarySelector { + relation: DoryAssistRelationId::G1ScalarMultiplicationBoundary, + endpoint: DoryAssistBoundaryEndpoint::Final, + } => Some(self.final_selector), + DoryAssistPublicId::BoundaryValue { + relation: DoryAssistRelationId::G1ScalarMultiplicationBoundary, + endpoint: DoryAssistBoundaryEndpoint::Initial, + component, + } => g1_point_component(&self.initial_value, component), + DoryAssistPublicId::BoundaryValue { + relation: DoryAssistRelationId::G1ScalarMultiplicationBoundary, + endpoint: DoryAssistBoundaryEndpoint::Final, + component, + } => g1_coordinate_component(&self.final_value, component), + _ => None, + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistG2PublicClaims { + pub scalar_multiplication_boundary: DoryAssistG2ScalarMultiplicationBoundaryPublicClaims, +} + +impl DoryAssistG2PublicClaims { + pub fn claim(&self, id: &DoryAssistPublicId) -> Option { + self.scalar_multiplication_boundary.claim(id) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistG2ScalarMultiplicationBoundaryPublicClaims { + pub initial_selector: Fq, + pub final_selector: Fq, + pub initial_value: DoryAssistG2PointClaims, + pub final_value: DoryAssistG2CoordinateClaims, +} + +impl Default for DoryAssistG2ScalarMultiplicationBoundaryPublicClaims { + fn default() -> Self { + Self { + initial_selector: Fq::from_u64(1), + final_selector: Fq::from_u64(1), + initial_value: DoryAssistG2PointClaims::default(), + final_value: DoryAssistG2CoordinateClaims::default(), + } + } +} + +impl DoryAssistG2ScalarMultiplicationBoundaryPublicClaims { + pub fn claim(&self, id: &DoryAssistPublicId) -> Option { + match *id { + DoryAssistPublicId::BoundarySelector { + relation: DoryAssistRelationId::G2ScalarMultiplicationBoundary, + endpoint: DoryAssistBoundaryEndpoint::Initial, + } => Some(self.initial_selector), + DoryAssistPublicId::BoundarySelector { + relation: DoryAssistRelationId::G2ScalarMultiplicationBoundary, + endpoint: DoryAssistBoundaryEndpoint::Final, + } => Some(self.final_selector), + DoryAssistPublicId::BoundaryValue { + relation: DoryAssistRelationId::G2ScalarMultiplicationBoundary, + endpoint: DoryAssistBoundaryEndpoint::Initial, + component, + } => g2_point_component(&self.initial_value, component), + DoryAssistPublicId::BoundaryValue { + relation: DoryAssistRelationId::G2ScalarMultiplicationBoundary, + endpoint: DoryAssistBoundaryEndpoint::Final, + component, + } => g2_coordinate_component(&self.final_value, component), + _ => None, + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistG1Claims { + pub scalar_multiplication: DoryAssistG1ScalarMultiplicationClaims, + pub scalar_multiplication_shift: DoryAssistG1ScalarMultiplicationShiftClaims, + pub scalar_multiplication_boundary: DoryAssistG1ScalarMultiplicationBoundaryClaims, + pub addition: DoryAssistG1AdditionClaims, +} + +impl DoryAssistG1Claims { + pub fn opening_claim(&self, id: &DoryAssistOpeningId) -> Option { + self.scalar_multiplication + .opening_claim(id) + .or_else(|| self.scalar_multiplication_shift.opening_claim(id)) + .or_else(|| self.scalar_multiplication_boundary.opening_claim(id)) + .or_else(|| self.addition.opening_claim(id)) + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistG1ScalarMultiplicationClaims { + pub accumulator: DoryAssistG1PointClaims, + pub doubled: DoryAssistG1PointClaims, + pub shifted_accumulator: DoryAssistG1CoordinateClaims, + pub bit: Fq, + pub base: DoryAssistG1CoordinateClaims, +} + +impl DoryAssistG1ScalarMultiplicationClaims { + pub fn opening_claim(&self, id: &DoryAssistOpeningId) -> Option { + match g1_polynomial(id, DoryAssistRelationId::G1ScalarMultiplication)? { + G1Polynomial::ScalarMulAccumulatorX => Some(self.accumulator.x), + G1Polynomial::ScalarMulAccumulatorY => Some(self.accumulator.y), + G1Polynomial::ScalarMulAccumulatorInfinity => Some(self.accumulator.infinity), + G1Polynomial::ScalarMulDoubledX => Some(self.doubled.x), + G1Polynomial::ScalarMulDoubledY => Some(self.doubled.y), + G1Polynomial::ScalarMulDoubledInfinity => Some(self.doubled.infinity), + G1Polynomial::ScalarMulShiftedAccumulatorX => Some(self.shifted_accumulator.x), + G1Polynomial::ScalarMulShiftedAccumulatorY => Some(self.shifted_accumulator.y), + G1Polynomial::ScalarMulBit => Some(self.bit), + G1Polynomial::ScalarMulBaseX => Some(self.base.x), + G1Polynomial::ScalarMulBaseY => Some(self.base.y), + _ => None, + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistG1ScalarMultiplicationShiftClaims { + pub shifted_accumulator: DoryAssistG1CoordinateClaims, + pub accumulator: DoryAssistG1CoordinateClaims, +} + +impl DoryAssistG1ScalarMultiplicationShiftClaims { + pub fn opening_claim(&self, id: &DoryAssistOpeningId) -> Option { + match *id { + id if id == g1::scalar_mul_shifted_accumulator_x_opening() => { + Some(self.shifted_accumulator.x) + } + id if id == g1::scalar_mul_shifted_accumulator_y_opening() => { + Some(self.shifted_accumulator.y) + } + id if id == g1::scalar_mul_accumulator_x_opening() => Some(self.accumulator.x), + id if id == g1::scalar_mul_accumulator_y_opening() => Some(self.accumulator.y), + _ => None, + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistG1ScalarMultiplicationBoundaryClaims { + pub accumulator: DoryAssistG1PointClaims, + pub shifted_accumulator: DoryAssistG1CoordinateClaims, +} + +impl DoryAssistG1ScalarMultiplicationBoundaryClaims { + pub fn opening_claim(&self, id: &DoryAssistOpeningId) -> Option { + match *id { + id if id == g1::scalar_mul_boundary_accumulator_x_opening() => Some(self.accumulator.x), + id if id == g1::scalar_mul_boundary_accumulator_y_opening() => Some(self.accumulator.y), + id if id == g1::scalar_mul_boundary_accumulator_infinity_opening() => { + Some(self.accumulator.infinity) + } + id if id == g1::scalar_mul_boundary_shifted_accumulator_x_opening() => { + Some(self.shifted_accumulator.x) + } + id if id == g1::scalar_mul_boundary_shifted_accumulator_y_opening() => { + Some(self.shifted_accumulator.y) + } + _ => None, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistG1AdditionClaims { + pub left: DoryAssistG1PointClaims, + pub right: DoryAssistG1PointClaims, + pub output: DoryAssistG1PointClaims, + pub slope: Fq, + pub inverse: Fq, + pub branch_selectors: [Fq; 2], +} + +impl Default for DoryAssistG1AdditionClaims { + fn default() -> Self { + Self { + left: DoryAssistG1PointClaims::default(), + right: DoryAssistG1PointClaims::default(), + output: DoryAssistG1PointClaims::default(), + slope: Fq::default(), + inverse: Fq::default(), + branch_selectors: [Fq::from_u64(1), Fq::default()], + } + } +} + +impl DoryAssistG1AdditionClaims { + pub fn opening_claim(&self, id: &DoryAssistOpeningId) -> Option { + match g1_polynomial(id, DoryAssistRelationId::G1Addition)? { + G1Polynomial::AddInputLeftX => Some(self.left.x), + G1Polynomial::AddInputLeftY => Some(self.left.y), + G1Polynomial::AddInputLeftInfinity => Some(self.left.infinity), + G1Polynomial::AddInputRightX => Some(self.right.x), + G1Polynomial::AddInputRightY => Some(self.right.y), + G1Polynomial::AddInputRightInfinity => Some(self.right.infinity), + G1Polynomial::AddOutputX => Some(self.output.x), + G1Polynomial::AddOutputY => Some(self.output.y), + G1Polynomial::AddOutputInfinity => Some(self.output.infinity), + G1Polynomial::AddSlope => Some(self.slope), + G1Polynomial::AddInverse => Some(self.inverse), + G1Polynomial::AddBranchSelector(index) => self.branch_selectors.get(index).copied(), + _ => None, + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistG2Claims { + pub scalar_multiplication: DoryAssistG2ScalarMultiplicationClaims, + pub scalar_multiplication_shift: DoryAssistG2ScalarMultiplicationShiftClaims, + pub scalar_multiplication_boundary: DoryAssistG2ScalarMultiplicationBoundaryClaims, + pub addition: DoryAssistG2AdditionClaims, +} + +impl DoryAssistG2Claims { + pub fn opening_claim(&self, id: &DoryAssistOpeningId) -> Option { + self.scalar_multiplication + .opening_claim(id) + .or_else(|| self.scalar_multiplication_shift.opening_claim(id)) + .or_else(|| self.scalar_multiplication_boundary.opening_claim(id)) + .or_else(|| self.addition.opening_claim(id)) + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistG2ScalarMultiplicationClaims { + pub accumulator: DoryAssistG2PointClaims, + pub doubled: DoryAssistG2PointClaims, + pub shifted_accumulator: DoryAssistG2CoordinateClaims, + pub bit: Fq, + pub base: DoryAssistG2CoordinateClaims, +} + +impl DoryAssistG2ScalarMultiplicationClaims { + pub fn opening_claim(&self, id: &DoryAssistOpeningId) -> Option { + match g2_polynomial(id, DoryAssistRelationId::G2ScalarMultiplication)? { + G2Polynomial::ScalarMulAccumulatorX0 => Some(self.accumulator.x[0]), + G2Polynomial::ScalarMulAccumulatorX1 => Some(self.accumulator.x[1]), + G2Polynomial::ScalarMulAccumulatorY0 => Some(self.accumulator.y[0]), + G2Polynomial::ScalarMulAccumulatorY1 => Some(self.accumulator.y[1]), + G2Polynomial::ScalarMulAccumulatorInfinity => Some(self.accumulator.infinity), + G2Polynomial::ScalarMulDoubledX0 => Some(self.doubled.x[0]), + G2Polynomial::ScalarMulDoubledX1 => Some(self.doubled.x[1]), + G2Polynomial::ScalarMulDoubledY0 => Some(self.doubled.y[0]), + G2Polynomial::ScalarMulDoubledY1 => Some(self.doubled.y[1]), + G2Polynomial::ScalarMulDoubledInfinity => Some(self.doubled.infinity), + G2Polynomial::ScalarMulShiftedAccumulatorX0 => Some(self.shifted_accumulator.x[0]), + G2Polynomial::ScalarMulShiftedAccumulatorX1 => Some(self.shifted_accumulator.x[1]), + G2Polynomial::ScalarMulShiftedAccumulatorY0 => Some(self.shifted_accumulator.y[0]), + G2Polynomial::ScalarMulShiftedAccumulatorY1 => Some(self.shifted_accumulator.y[1]), + G2Polynomial::ScalarMulBit => Some(self.bit), + G2Polynomial::ScalarMulBaseX0 => Some(self.base.x[0]), + G2Polynomial::ScalarMulBaseX1 => Some(self.base.x[1]), + G2Polynomial::ScalarMulBaseY0 => Some(self.base.y[0]), + G2Polynomial::ScalarMulBaseY1 => Some(self.base.y[1]), + _ => None, + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistG2ScalarMultiplicationShiftClaims { + pub shifted_accumulator: DoryAssistG2CoordinateClaims, + pub accumulator: DoryAssistG2CoordinateClaims, +} + +impl DoryAssistG2ScalarMultiplicationShiftClaims { + pub fn opening_claim(&self, id: &DoryAssistOpeningId) -> Option { + match *id { + id if id == g2::scalar_mul_shifted_accumulator_x0_opening() => { + Some(self.shifted_accumulator.x[0]) + } + id if id == g2::scalar_mul_shifted_accumulator_x1_opening() => { + Some(self.shifted_accumulator.x[1]) + } + id if id == g2::scalar_mul_shifted_accumulator_y0_opening() => { + Some(self.shifted_accumulator.y[0]) + } + id if id == g2::scalar_mul_shifted_accumulator_y1_opening() => { + Some(self.shifted_accumulator.y[1]) + } + id if id == g2::scalar_mul_accumulator_x0_opening() => Some(self.accumulator.x[0]), + id if id == g2::scalar_mul_accumulator_x1_opening() => Some(self.accumulator.x[1]), + id if id == g2::scalar_mul_accumulator_y0_opening() => Some(self.accumulator.y[0]), + id if id == g2::scalar_mul_accumulator_y1_opening() => Some(self.accumulator.y[1]), + _ => None, + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistG2ScalarMultiplicationBoundaryClaims { + pub accumulator: DoryAssistG2PointClaims, + pub shifted_accumulator: DoryAssistG2CoordinateClaims, +} + +impl DoryAssistG2ScalarMultiplicationBoundaryClaims { + pub fn opening_claim(&self, id: &DoryAssistOpeningId) -> Option { + match *id { + id if id == g2::scalar_mul_boundary_accumulator_x0_opening() => { + Some(self.accumulator.x[0]) + } + id if id == g2::scalar_mul_boundary_accumulator_x1_opening() => { + Some(self.accumulator.x[1]) + } + id if id == g2::scalar_mul_boundary_accumulator_y0_opening() => { + Some(self.accumulator.y[0]) + } + id if id == g2::scalar_mul_boundary_accumulator_y1_opening() => { + Some(self.accumulator.y[1]) + } + id if id == g2::scalar_mul_boundary_accumulator_infinity_opening() => { + Some(self.accumulator.infinity) + } + id if id == g2::scalar_mul_boundary_shifted_accumulator_x0_opening() => { + Some(self.shifted_accumulator.x[0]) + } + id if id == g2::scalar_mul_boundary_shifted_accumulator_x1_opening() => { + Some(self.shifted_accumulator.x[1]) + } + id if id == g2::scalar_mul_boundary_shifted_accumulator_y0_opening() => { + Some(self.shifted_accumulator.y[0]) + } + id if id == g2::scalar_mul_boundary_shifted_accumulator_y1_opening() => { + Some(self.shifted_accumulator.y[1]) + } + _ => None, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistG2AdditionClaims { + pub left: DoryAssistG2PointClaims, + pub right: DoryAssistG2PointClaims, + pub output: DoryAssistG2PointClaims, + pub slope: [Fq; 2], + pub inverse: [Fq; 2], + pub branch_selectors: [Fq; 2], +} + +impl Default for DoryAssistG2AdditionClaims { + fn default() -> Self { + Self { + left: DoryAssistG2PointClaims::default(), + right: DoryAssistG2PointClaims::default(), + output: DoryAssistG2PointClaims::default(), + slope: [Fq::default(); 2], + inverse: [Fq::default(); 2], + branch_selectors: [Fq::from_u64(1), Fq::default()], + } + } +} + +impl DoryAssistG2AdditionClaims { + pub fn opening_claim(&self, id: &DoryAssistOpeningId) -> Option { + match g2_polynomial(id, DoryAssistRelationId::G2Addition)? { + G2Polynomial::AddInputLeftX0 => Some(self.left.x[0]), + G2Polynomial::AddInputLeftX1 => Some(self.left.x[1]), + G2Polynomial::AddInputLeftY0 => Some(self.left.y[0]), + G2Polynomial::AddInputLeftY1 => Some(self.left.y[1]), + G2Polynomial::AddInputLeftInfinity => Some(self.left.infinity), + G2Polynomial::AddInputRightX0 => Some(self.right.x[0]), + G2Polynomial::AddInputRightX1 => Some(self.right.x[1]), + G2Polynomial::AddInputRightY0 => Some(self.right.y[0]), + G2Polynomial::AddInputRightY1 => Some(self.right.y[1]), + G2Polynomial::AddInputRightInfinity => Some(self.right.infinity), + G2Polynomial::AddOutputX0 => Some(self.output.x[0]), + G2Polynomial::AddOutputX1 => Some(self.output.x[1]), + G2Polynomial::AddOutputY0 => Some(self.output.y[0]), + G2Polynomial::AddOutputY1 => Some(self.output.y[1]), + G2Polynomial::AddOutputInfinity => Some(self.output.infinity), + G2Polynomial::AddSlope0 => Some(self.slope[0]), + G2Polynomial::AddSlope1 => Some(self.slope[1]), + G2Polynomial::AddInverse0 => Some(self.inverse[0]), + G2Polynomial::AddInverse1 => Some(self.inverse[1]), + G2Polynomial::AddBranchSelector(index) => self.branch_selectors.get(index).copied(), + _ => None, + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistMillerLoopClaims { + pub line_step: DoryAssistMillerLoopLineStepClaims, + pub line_evaluation: DoryAssistMillerLoopLineEvaluationClaims, + pub pair_product: DoryAssistMillerLoopPairProductClaims, + pub accumulator: DoryAssistMillerLoopAccumulatorClaims, + pub boundary: DoryAssistMillerLoopBoundaryClaims, +} + +impl DoryAssistMillerLoopClaims { + pub fn opening_claim(&self, id: &DoryAssistOpeningId) -> Option { + self.line_step + .opening_claim(id) + .or_else(|| self.line_evaluation.opening_claim(id)) + .or_else(|| self.pair_product.opening_claim(id)) + .or_else(|| self.accumulator.opening_claim(id)) + .or_else(|| self.boundary.opening_claim(id)) + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistMillerLoopLineStepClaims { + pub state_x: [Fq; 2], + pub state_y: [Fq; 2], + pub state_z: [Fq; 2], + pub addend_x: [Fq; 2], + pub addend_y: [Fq; 2], + pub shifted_state_x: [Fq; 2], + pub shifted_state_y: [Fq; 2], + pub shifted_state_z: [Fq; 2], + pub line_coefficients: [[Fq; 2]; miller_loop::MILLER_LOOP_LINE_COEFFICIENTS], +} + +impl DoryAssistMillerLoopLineStepClaims { + pub fn opening_claim(&self, id: &DoryAssistOpeningId) -> Option { + match miller_loop_polynomial(id, DoryAssistRelationId::MillerLoopLineStep)? { + MillerLoopPolynomial::G2LineStateX0 => Some(self.state_x[0]), + MillerLoopPolynomial::G2LineStateX1 => Some(self.state_x[1]), + MillerLoopPolynomial::G2LineStateY0 => Some(self.state_y[0]), + MillerLoopPolynomial::G2LineStateY1 => Some(self.state_y[1]), + MillerLoopPolynomial::G2LineStateZ0 => Some(self.state_z[0]), + MillerLoopPolynomial::G2LineStateZ1 => Some(self.state_z[1]), + MillerLoopPolynomial::G2LineAddendX0 => Some(self.addend_x[0]), + MillerLoopPolynomial::G2LineAddendX1 => Some(self.addend_x[1]), + MillerLoopPolynomial::G2LineAddendY0 => Some(self.addend_y[0]), + MillerLoopPolynomial::G2LineAddendY1 => Some(self.addend_y[1]), + MillerLoopPolynomial::G2LineShiftedStateX0 => Some(self.shifted_state_x[0]), + MillerLoopPolynomial::G2LineShiftedStateX1 => Some(self.shifted_state_x[1]), + MillerLoopPolynomial::G2LineShiftedStateY0 => Some(self.shifted_state_y[0]), + MillerLoopPolynomial::G2LineShiftedStateY1 => Some(self.shifted_state_y[1]), + MillerLoopPolynomial::G2LineShiftedStateZ0 => Some(self.shifted_state_z[0]), + MillerLoopPolynomial::G2LineShiftedStateZ1 => Some(self.shifted_state_z[1]), + MillerLoopPolynomial::LineCoefficient { + coefficient, + component, + } => self + .line_coefficients + .get(coefficient) + .and_then(|coefficient| coefficient.get(component)) + .copied(), + _ => None, + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistMillerLoopLineEvaluationClaims { + pub g1_point_x: Fq, + pub g1_point_y: Fq, + pub line_coefficients: [[Fq; 2]; miller_loop::MILLER_LOOP_LINE_COEFFICIENTS], + pub line_evaluation_coeffs: [Fq; miller_loop::MILLER_LOOP_GT_COEFFS], +} + +impl DoryAssistMillerLoopLineEvaluationClaims { + pub fn opening_claim(&self, id: &DoryAssistOpeningId) -> Option { + match miller_loop_polynomial(id, DoryAssistRelationId::MillerLoopLineEvaluation)? { + MillerLoopPolynomial::G1PointX => Some(self.g1_point_x), + MillerLoopPolynomial::G1PointY => Some(self.g1_point_y), + MillerLoopPolynomial::LineCoefficient { + coefficient, + component, + } => self + .line_coefficients + .get(coefficient) + .and_then(|coefficient| coefficient.get(component)) + .copied(), + MillerLoopPolynomial::LineEvaluationCoeff(component) => { + self.line_evaluation_coeffs.get(component).copied() + } + _ => None, + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistMillerLoopPairProductClaims { + pub accumulator: [Fq; miller_loop::MILLER_LOOP_GT_COEFFS], + pub shifted_accumulator: [Fq; miller_loop::MILLER_LOOP_GT_COEFFS], + pub line_product: [Fq; miller_loop::MILLER_LOOP_GT_COEFFS], + pub quotient: [Fq; miller_loop::MILLER_LOOP_GT_COEFFS], +} + +impl DoryAssistMillerLoopPairProductClaims { + pub fn opening_claim(&self, id: &DoryAssistOpeningId) -> Option { + match miller_loop_polynomial(id, DoryAssistRelationId::MillerLoopPairProduct)? { + MillerLoopPolynomial::PairProductAccumulatorCoeff(component) => { + self.accumulator.get(component).copied() + } + MillerLoopPolynomial::PairProductShiftedAccumulatorCoeff(component) => { + self.shifted_accumulator.get(component).copied() + } + MillerLoopPolynomial::PairLineProductCoeff(component) => { + self.line_product.get(component).copied() + } + MillerLoopPolynomial::PairProductQuotientCoeff(component) => { + self.quotient.get(component).copied() + } + _ => None, + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistMillerLoopAccumulatorClaims { + pub accumulator: [Fq; miller_loop::MILLER_LOOP_GT_COEFFS], + pub shifted_accumulator: [Fq; miller_loop::MILLER_LOOP_GT_COEFFS], + pub quotient: [Fq; miller_loop::MILLER_LOOP_GT_COEFFS], +} + +impl DoryAssistMillerLoopAccumulatorClaims { + pub fn opening_claim(&self, id: &DoryAssistOpeningId) -> Option { + match miller_loop_polynomial(id, DoryAssistRelationId::MillerLoopAccumulator)? { + MillerLoopPolynomial::AccumulatorCoeff(component) => { + self.accumulator.get(component).copied() + } + MillerLoopPolynomial::ShiftedAccumulatorCoeff(component) => { + self.shifted_accumulator.get(component).copied() + } + MillerLoopPolynomial::AccumulatorQuotientCoeff(component) => { + self.quotient.get(component).copied() + } + _ => None, + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistMillerLoopBoundaryClaims { + pub accumulator: [Fq; miller_loop::MILLER_LOOP_GT_COEFFS], + pub shifted_accumulator: [Fq; miller_loop::MILLER_LOOP_GT_COEFFS], +} + +impl DoryAssistMillerLoopBoundaryClaims { + pub fn opening_claim(&self, id: &DoryAssistOpeningId) -> Option { + match miller_loop_polynomial(id, DoryAssistRelationId::MillerLoopBoundary)? { + MillerLoopPolynomial::AccumulatorCoeff(component) => { + self.accumulator.get(component).copied() + } + MillerLoopPolynomial::ShiftedAccumulatorCoeff(component) => { + self.shifted_accumulator.get(component).copied() + } + _ => None, + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistDoryReduceClaims { + pub transitions: Vec, + pub scalar_fold: DoryAssistDoryReduceScalarFoldClaims, + pub state_chain: Vec, + pub boundary: Vec, +} + +impl DoryAssistDoryReduceClaims { + pub fn opening_claim(&self, id: &DoryAssistOpeningId) -> Option { + self.scalar_fold + .opening_claim(id) + .or_else(|| { + self.transitions + .iter() + .find(|claim| claim.id == *id) + .map(|claim| claim.value) + }) + .or_else(|| { + self.state_chain + .iter() + .find(|claim| claim.id == *id) + .map(|claim| claim.value) + }) + .or_else(|| { + self.boundary + .iter() + .find(|claim| claim.id == *id) + .map(|claim| claim.value) + }) + } +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistDoryReduceScalarFoldClaims { + pub s1_accumulator: Fq, + pub s1_next_accumulator: Fq, + pub s1_fold_factor: Fq, + pub s2_accumulator: Fq, + pub s2_next_accumulator: Fq, + pub s2_fold_factor: Fq, +} + +impl DoryAssistDoryReduceScalarFoldClaims { + pub fn opening_claim(&self, id: &DoryAssistOpeningId) -> Option { + match dory_reduce_polynomial(id, DoryAssistRelationId::DoryReduceScalarFold)? { + DoryReducePolynomial::S1Accumulator => Some(self.s1_accumulator), + DoryReducePolynomial::S1NextAccumulator => Some(self.s1_next_accumulator), + DoryReducePolynomial::S1FoldFactor => Some(self.s1_fold_factor), + DoryReducePolynomial::S2Accumulator => Some(self.s2_accumulator), + DoryReducePolynomial::S2NextAccumulator => Some(self.s2_next_accumulator), + DoryReducePolynomial::S2FoldFactor => Some(self.s2_fold_factor), + _ => None, + } + } +} + +fn g1_point_component(claims: &DoryAssistG1PointClaims, component: usize) -> Option { + match component { + 0 => Some(claims.x), + 1 => Some(claims.y), + 2 => Some(claims.infinity), + _ => None, + } +} + +fn g1_coordinate_component(claims: &DoryAssistG1CoordinateClaims, component: usize) -> Option { + match component { + 0 => Some(claims.x), + 1 => Some(claims.y), + _ => None, + } +} + +fn g2_point_component(claims: &DoryAssistG2PointClaims, component: usize) -> Option { + match component { + 0 => Some(claims.x[0]), + 1 => Some(claims.x[1]), + 2 => Some(claims.y[0]), + 3 => Some(claims.y[1]), + 4 => Some(claims.infinity), + _ => None, + } +} + +fn g2_coordinate_component(claims: &DoryAssistG2CoordinateClaims, component: usize) -> Option { + match component { + 0 => Some(claims.x[0]), + 1 => Some(claims.x[1]), + 2 => Some(claims.y[0]), + 3 => Some(claims.y[1]), + _ => None, + } +} + +fn g1_polynomial( + id: &DoryAssistOpeningId, + expected_relation: DoryAssistRelationId, +) -> Option { + match *id { + DoryAssistOpeningId::Polynomial { + polynomial: DoryAssistPolynomialId::Virtual(DoryAssistVirtualPolynomial::G1(polynomial)), + relation, + } if relation == expected_relation => Some(polynomial), + DoryAssistOpeningId::Polynomial { .. } => None, + } +} + +fn g2_polynomial( + id: &DoryAssistOpeningId, + expected_relation: DoryAssistRelationId, +) -> Option { + match *id { + DoryAssistOpeningId::Polynomial { + polynomial: DoryAssistPolynomialId::Virtual(DoryAssistVirtualPolynomial::G2(polynomial)), + relation, + } if relation == expected_relation => Some(polynomial), + DoryAssistOpeningId::Polynomial { .. } => None, + } +} + +fn miller_loop_polynomial( + id: &DoryAssistOpeningId, + expected_relation: DoryAssistRelationId, +) -> Option { + match *id { + DoryAssistOpeningId::Polynomial { + polynomial: + DoryAssistPolynomialId::Virtual(DoryAssistVirtualPolynomial::MillerLoop(polynomial)), + relation, + } if relation == expected_relation => Some(polynomial), + DoryAssistOpeningId::Polynomial { .. } => None, + } +} + +fn dory_reduce_polynomial( + id: &DoryAssistOpeningId, + expected_relation: DoryAssistRelationId, +) -> Option { + match *id { + DoryAssistOpeningId::Polynomial { + polynomial: + DoryAssistPolynomialId::Virtual(DoryAssistVirtualPolynomial::DoryReduce(polynomial)), + relation, + } if relation == expected_relation => Some(polynomial), + DoryAssistOpeningId::Polynomial { .. } => None, + } +} + +fn gt_polynomial( + id: &DoryAssistOpeningId, + expected_relation: DoryAssistRelationId, +) -> Option { + match *id { + DoryAssistOpeningId::Polynomial { + polynomial: DoryAssistPolynomialId::Virtual(DoryAssistVirtualPolynomial::Gt(polynomial)), + relation, + } if relation == expected_relation => Some(polynomial), + DoryAssistOpeningId::Polynomial { .. } => None, + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistOpeningClaim { + pub id: DoryAssistOpeningId, + pub value: Fq, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistOpeningClaims { + pub packed_point: Vec, + pub packed_eval: Fq, +} + +impl Default for DoryAssistProof { + fn default() -> Self { + Self { + dimensions: default_dory_assist_dimensions(), + stages: DoryAssistStageProofs::default(), + opening_proof: HyraxOpeningProof { + combined_row: Vec::new(), + combined_row_opening_scalar: Fq::default(), + }, + claims: DoryAssistProofClaims::default(), + dense_commitment: HyraxCommitment::default(), + public_outputs: DoryAssistPublicOutputs::default(), + } + } +} + +#[expect( + clippy::expect_used, + reason = "canonical default Dory-assist dimensions are statically valid" +)] +pub fn default_dory_assist_dimensions() -> DoryAssistDimensions { + let unpacked = DoryAssistDimensions::new( + GtDimensions::new(7, 2, 3), + G1Dimensions::new(8, 2, 3), + G2Dimensions::new(8, 2, 3), + MillerLoopDimensions::new(7, 2, 8), + DoryReduceDimensions::new(2, 1), + WiringDimensions::new(6), + PrefixPackingDimensions::new(0, 0, 0).expect("valid empty packing dimensions"), + ); + let packing = composition::prefix_packing_catalog(unpacked) + .minimal_dimensions() + .expect("valid canonical packing dimensions"); + + DoryAssistDimensions::new( + unpacked.gt, + unpacked.g1, + unpacked.g2, + unpacked.miller_loop, + unpacked.dory_reduce, + unpacked.wiring, + packing, + ) +} diff --git a/crates/jolt-dory-assist-verifier/src/setup.rs b/crates/jolt-dory-assist-verifier/src/setup.rs new file mode 100644 index 0000000000..13f706f8c0 --- /dev/null +++ b/crates/jolt-dory-assist-verifier/src/setup.rs @@ -0,0 +1,98 @@ +//! Dory-assist Hyrax setup derivation. + +use jolt_crypto::{GrumpkinPedersenSetupSeed, GrumpkinPoint, Pedersen}; +use jolt_hyrax::{HyraxDimensions, HyraxError, HyraxProverSetup, HyraxScheme, HyraxVerifierSetup}; + +pub type DoryAssistHyrax = HyraxScheme>; +pub type DoryAssistHyraxProverSetup = HyraxProverSetup>; +pub type DoryAssistHyraxVerifierSetup = HyraxVerifierSetup>; + +pub const DORY_ASSIST_HYRAX_GRUMPKIN_DOMAIN: &[u8] = b"JoltDoryAssistHyraxGrumpkin"; +pub const DORY_ASSIST_HYRAX_GRUMPKIN_SEED: &[u8] = b"v1"; +pub const DORY_ASSIST_HYRAX_GRUMPKIN_SETUP_SEED: GrumpkinPedersenSetupSeed<'static> = + GrumpkinPedersenSetupSeed::new( + DORY_ASSIST_HYRAX_GRUMPKIN_DOMAIN, + DORY_ASSIST_HYRAX_GRUMPKIN_SEED, + ); + +pub fn derive_hyrax_prover_setup( + dimensions: HyraxDimensions, +) -> Result { + DoryAssistHyraxProverSetup::derive_from(&DORY_ASSIST_HYRAX_GRUMPKIN_SETUP_SEED, dimensions) +} + +pub fn derive_hyrax_verifier_setup( + dimensions: HyraxDimensions, +) -> Result { + DoryAssistHyraxVerifierSetup::derive_from(&DORY_ASSIST_HYRAX_GRUMPKIN_SETUP_SEED, dimensions) +} + +#[cfg(test)] +#[expect(clippy::expect_used, reason = "tests may panic on assertion failures")] +mod tests { + use jolt_field::{Fq, FromPrimitiveInt}; + use jolt_openings::CommitmentScheme; + use jolt_poly::Polynomial; + use jolt_transcript::{Blake2bTranscript, Transcript}; + + use super::*; + + fn dimensions() -> HyraxDimensions { + HyraxDimensions::new(3, 1, 2).expect("valid dimensions") + } + + fn polynomial() -> Polynomial { + Polynomial::from( + (0..8) + .map(|index| Fq::from_u64(index + 3)) + .collect::>(), + ) + } + + #[test] + fn dory_assist_seed_derives_matching_hyrax_setups() { + let dimensions = dimensions(); + let prover_setup = + derive_hyrax_prover_setup(dimensions).expect("derive prover setup from seed"); + let verifier_setup = + derive_hyrax_verifier_setup(dimensions).expect("derive verifier setup from seed"); + + assert_eq!(prover_setup.dimensions, verifier_setup.dimensions); + assert_eq!(prover_setup.vc_setup, verifier_setup.vc_setup); + assert_eq!(prover_setup.vc_setup.message_generators.len(), 4); + } + + #[test] + fn seed_derived_setup_verifies_hyrax_opening() { + let dimensions = dimensions(); + let prover_setup = + derive_hyrax_prover_setup(dimensions).expect("derive prover setup from seed"); + let verifier_setup = + derive_hyrax_verifier_setup(dimensions).expect("derive verifier setup from seed"); + let poly = polynomial(); + let point = vec![Fq::from_u64(2), Fq::from_u64(3), Fq::from_u64(5)]; + let eval = poly.evaluate(&point); + let (commitment, hint) = DoryAssistHyrax::commit(&poly, &prover_setup); + + let mut prover_transcript = Blake2bTranscript::new(b"dory-assist-hyrax-seed"); + let proof = DoryAssistHyrax::open( + &poly, + &point, + eval, + &prover_setup, + Some(hint), + &mut prover_transcript, + ); + + let mut verifier_transcript = Blake2bTranscript::new(b"dory-assist-hyrax-seed"); + DoryAssistHyrax::verify( + &commitment, + &point, + eval, + &proof, + &verifier_setup, + &mut verifier_transcript, + ) + .expect("seed-derived Hyrax opening verifies"); + } +} diff --git a/crates/jolt-dory-assist-verifier/src/stages/mod.rs b/crates/jolt-dory-assist-verifier/src/stages/mod.rs new file mode 100644 index 0000000000..f31f9be4f8 --- /dev/null +++ b/crates/jolt-dory-assist-verifier/src/stages/mod.rs @@ -0,0 +1,14 @@ +//! Dory-assist verifier stages. + +pub mod stage1; +pub mod stage2; +pub mod stage3; + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct DoryAssistStageProofs { + pub stage1: stage1::Stage1Proof, + pub stage2: stage2::Stage2Proof, + pub stage3: stage3::Stage3Proof, +} diff --git a/crates/jolt-dory-assist-verifier/src/stages/stage1/inputs.rs b/crates/jolt-dory-assist-verifier/src/stages/stage1/inputs.rs new file mode 100644 index 0000000000..63c5df6bda --- /dev/null +++ b/crates/jolt-dory-assist-verifier/src/stages/stage1/inputs.rs @@ -0,0 +1,194 @@ +//! Typed inputs consumed by stage 1. + +use jolt_claims::protocols::dory_assist::{ + formulas::protocol::protocol_claims, DoryAssistDimensions, DoryAssistRelationId, + DoryAssistSumcheckSpec, +}; +use jolt_field::Fq; +use jolt_poly::CompressedPoly; +use jolt_sumcheck::CompressedSumcheckProof; +use serde::{Deserialize, Serialize}; + +use crate::verifier::CheckedInputs; + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct Stage1Proof { + pub relations: Vec, +} + +impl Stage1Proof { + pub fn canonical_for_dimensions(dimensions: DoryAssistDimensions) -> Self { + Self { + relations: canonical_stage1_relation_specs(dimensions) + .into_iter() + .map(Stage1RelationProof::zero_claim) + .collect(), + } + } + + pub fn relation_count(&self) -> u32 { + self.relations.len() as u32 + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct Stage1RelationProof { + pub id: DoryAssistRelationId, + pub sumcheck: DoryAssistSumcheckSpec, + pub sumcheck_proof: CompressedSumcheckProof, +} + +impl Stage1RelationProof { + fn zero_claim(spec: Stage1RelationSpec) -> Self { + Self { + id: spec.id, + sumcheck: spec.sumcheck, + sumcheck_proof: zero_compressed_sumcheck_proof(spec.sumcheck), + } + } + + pub(crate) const fn spec(&self) -> Stage1RelationSpec { + Stage1RelationSpec { + id: self.id, + sumcheck: self.sumcheck, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) struct Stage1RelationSpec { + pub id: DoryAssistRelationId, + pub sumcheck: DoryAssistSumcheckSpec, +} + +#[derive(Clone, Copy)] +pub struct Stage1Inputs<'a, 'p> { + pub checked: &'a CheckedInputs<'p>, + pub dimensions: DoryAssistDimensions, + pub proof: &'a Stage1Proof, + pub claims: &'a crate::proof::DoryAssistProofClaims, +} + +#[expect( + clippy::expect_used, + reason = "stage 1 relation IDs are a subset of the canonical protocol catalog" +)] +pub(crate) fn canonical_stage1_relation_specs( + dimensions: DoryAssistDimensions, +) -> Vec { + let protocol = protocol_claims::(dimensions); + canonical_stage1_relation_ids(dimensions) + .iter() + .map(|id| { + let relation = protocol + .relation(*id) + .expect("stage 1 relation belongs to canonical Dory-assist protocol"); + Stage1RelationSpec { + id: relation.id, + sumcheck: relation.sumcheck, + } + }) + .collect() +} + +pub(crate) fn canonical_stage1_relation_ids( + dimensions: DoryAssistDimensions, +) -> Vec { + let mut ids = BASE_STAGE1_RELATION_IDS.to_vec(); + if dimensions.dory_reduce.reduce_rounds() > 1 { + ids.push(DoryAssistRelationId::DoryReduceStateChain); + ids.push(DoryAssistRelationId::DoryReduceBoundary); + } + ids +} + +fn zero_compressed_sumcheck_proof(sumcheck: DoryAssistSumcheckSpec) -> CompressedSumcheckProof { + CompressedSumcheckProof { + round_polynomials: (0..sumcheck.rounds) + .map(|_| CompressedPoly::new(vec![Fq::default(); sumcheck.degree])) + .collect(), + } +} + +pub(crate) const BASE_STAGE1_RELATION_IDS: [DoryAssistRelationId; 24] = [ + DoryAssistRelationId::GtExponentiation, + DoryAssistRelationId::GtExponentiationDigitSelector, + DoryAssistRelationId::GtExponentiationBasePower, + DoryAssistRelationId::GtExponentiationDigitBitness, + DoryAssistRelationId::GtExponentiationShift, + DoryAssistRelationId::GtExponentiationBoundary, + DoryAssistRelationId::GtMultiplication, + DoryAssistRelationId::G1ScalarMultiplication, + DoryAssistRelationId::G1ScalarMultiplicationShift, + DoryAssistRelationId::G1ScalarMultiplicationBoundary, + DoryAssistRelationId::G1Addition, + DoryAssistRelationId::G2ScalarMultiplication, + DoryAssistRelationId::G2ScalarMultiplicationShift, + DoryAssistRelationId::G2ScalarMultiplicationBoundary, + DoryAssistRelationId::G2Addition, + DoryAssistRelationId::MillerLoopLineStep, + DoryAssistRelationId::MillerLoopLineEvaluation, + DoryAssistRelationId::MillerLoopPairProduct, + DoryAssistRelationId::MillerLoopAccumulator, + DoryAssistRelationId::MillerLoopBoundary, + DoryAssistRelationId::DoryReduceGtTransition, + DoryAssistRelationId::DoryReduceG1Transition, + DoryAssistRelationId::DoryReduceG2Transition, + DoryAssistRelationId::DoryReduceScalarFold, +]; + +#[cfg(test)] +mod tests { + #![expect( + clippy::expect_used, + reason = "tests fail loudly on invalid fixture dimensions" + )] + + use super::*; + use jolt_claims::protocols::dory_assist::{ + DoryAssistDimensions, DoryReduceDimensions, G1Dimensions, G2Dimensions, GtDimensions, + MillerLoopDimensions, PrefixPackingDimensions, WiringDimensions, + }; + + fn dimensions(reduce_rounds: usize) -> DoryAssistDimensions { + DoryAssistDimensions::new( + GtDimensions::new(7, 2, 3), + G1Dimensions::new(8, 2, 3), + G2Dimensions::new(8, 2, 3), + MillerLoopDimensions::new(7, 2, 8), + DoryReduceDimensions::new(2 * reduce_rounds, reduce_rounds), + WiringDimensions::new(6), + PrefixPackingDimensions::new(0, 0, 0).expect("valid empty packing dimensions"), + ) + } + + #[test] + fn singleton_stage1_relation_catalog_omits_dory_reduce_state_chain() { + let ids = canonical_stage1_relation_ids(dimensions(1)); + + assert_eq!(ids, BASE_STAGE1_RELATION_IDS); + assert!(!ids.contains(&DoryAssistRelationId::DoryReduceStateChain)); + } + + #[test] + fn multiround_stage1_relation_catalog_includes_dory_reduce_state_chain_and_boundary() { + let dimensions = dimensions(2); + let ids = canonical_stage1_relation_ids(dimensions); + let specs = canonical_stage1_relation_specs(dimensions); + + assert_eq!(ids.len(), BASE_STAGE1_RELATION_IDS.len() + 2); + assert_eq!( + ids[BASE_STAGE1_RELATION_IDS.len()], + DoryAssistRelationId::DoryReduceStateChain + ); + assert_eq!(ids.last(), Some(&DoryAssistRelationId::DoryReduceBoundary)); + assert!(specs.iter().any(|spec| { + spec.id == DoryAssistRelationId::DoryReduceStateChain + && spec.sumcheck == dimensions.dory_reduce.state_chain_sumcheck() + })); + assert!(specs.iter().any(|spec| { + spec.id == DoryAssistRelationId::DoryReduceBoundary + && spec.sumcheck == dimensions.dory_reduce.boundary_sumcheck() + })); + } +} diff --git a/crates/jolt-dory-assist-verifier/src/stages/stage1/mod.rs b/crates/jolt-dory-assist-verifier/src/stages/stage1/mod.rs new file mode 100644 index 0000000000..4da44a663e --- /dev/null +++ b/crates/jolt-dory-assist-verifier/src/stages/stage1/mod.rs @@ -0,0 +1,9 @@ +//! Stage 1 algebraic relation verifier. + +pub mod inputs; +pub mod outputs; +mod verify; + +pub use inputs::{Stage1Inputs, Stage1Proof, Stage1RelationProof}; +pub use outputs::Stage1Output; +pub use verify::verify; diff --git a/crates/jolt-dory-assist-verifier/src/stages/stage1/outputs.rs b/crates/jolt-dory-assist-verifier/src/stages/stage1/outputs.rs new file mode 100644 index 0000000000..592aa4f4d0 --- /dev/null +++ b/crates/jolt-dory-assist-verifier/src/stages/stage1/outputs.rs @@ -0,0 +1,31 @@ +//! Typed outputs produced by stage 1 verification. + +use jolt_claims::protocols::dory_assist::{DoryAssistChallengeId, DoryAssistRelationId}; +use jolt_field::Fq; +use jolt_poly::{Point, HIGH_TO_LOW}; + +use crate::proof::DoryAssistOpeningClaim; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage1Output { + pub relation_count: u32, + pub relation_outputs: Vec, + pub challenge: Fq, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage1RelationOutput { + pub id: DoryAssistRelationId, + pub relation_challenges: Vec, + pub input_claim: Fq, + pub sumcheck_point: Point, + pub sumcheck_final_claim: Fq, + pub expected_output_claim: Fq, + pub opening_claims: Vec, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct DoryAssistChallengeValue { + pub id: DoryAssistChallengeId, + pub value: Fq, +} diff --git a/crates/jolt-dory-assist-verifier/src/stages/stage1/verify.rs b/crates/jolt-dory-assist-verifier/src/stages/stage1/verify.rs new file mode 100644 index 0000000000..f5efb7abd3 --- /dev/null +++ b/crates/jolt-dory-assist-verifier/src/stages/stage1/verify.rs @@ -0,0 +1,421 @@ +use jolt_claims::protocols::dory_assist::{ + formulas::protocol::{protocol_claims, CANONICAL_RELATION_ORDER}, + DoryAssistChallengeId, DoryAssistExpr, DoryAssistOpeningId, DoryAssistPublicId, + DoryAssistRelationClaims, DoryAssistSumcheckDomain, +}; +use jolt_claims::ConsistencyClaim; +use jolt_dory::DoryScheme; +use jolt_field::Fq; +use jolt_openings::{CommitmentScheme, EvaluationClaim}; +use jolt_poly::UnivariatePolynomial; +use jolt_sumcheck::{SumcheckClaim, SUMCHECK_ROUND_TRANSCRIPT_LABEL}; +use jolt_transcript::{Label, LabelWithCount, Transcript, U64Word}; + +use super::{ + inputs::{canonical_stage1_relation_specs, Stage1Inputs, Stage1RelationProof}, + outputs::{DoryAssistChallengeValue, Stage1Output, Stage1RelationOutput}, +}; +use crate::{ + proof::{DoryAssistOpeningClaim, DoryAssistProofClaims}, + verifier::{squeeze_fq, squeeze_fq_challenge}, + DoryAssistStage, DoryAssistVerifierError, +}; + +pub fn verify( + inputs: Stage1Inputs<'_, '_>, + transcript: &mut T, +) -> Result +where + T: Transcript::Field>, +{ + if inputs.proof.relations.is_empty() { + return Err(DoryAssistVerifierError::InvalidProofShape { + component: "stage1.relations", + reason: "relations must be nonempty".to_string(), + }); + } + + let expected_relations = canonical_stage1_relation_specs(inputs.dimensions); + let actual_relations = inputs + .proof + .relations + .iter() + .map(Stage1RelationProof::spec) + .collect::>(); + if actual_relations != expected_relations { + return Err(DoryAssistVerifierError::StageClaimMismatch { + stage: DoryAssistStage::Stage1, + reason: format!( + "stage 1 relations must match canonical Dory-assist relation catalog: expected {expected_relations:?}, got {:?}", + actual_relations + ), + }); + } + + transcript.append(&Label(b"dory_assist_stage1")); + transcript.append(&Label(inputs.checked.mode_name().as_bytes())); + let semantic_relations = canonical_stage1_relation_claims(inputs.dimensions); + + transcript.append(&Label(b"stage1_relations")); + transcript.append(&U64Word(inputs.proof.relation_count() as u64)); + let mut relation_outputs = Vec::with_capacity(inputs.proof.relations.len()); + for (relation, semantic_relation) in inputs.proof.relations.iter().zip(&semantic_relations) { + absorb_relation(relation, transcript); + + let relation_challenges = sample_relation_challenges(semantic_relation, transcript); + let input_claim = evaluate_relation_expression( + semantic_relation.input.expression(), + inputs.claims, + &relation_challenges, + )?; + let reduction = verify_relation_sumcheck(relation, input_claim, transcript)?; + let expected_output_claim = evaluate_relation_expression( + semantic_relation.output.expression(), + inputs.claims, + &relation_challenges, + )?; + if reduction.value != expected_output_claim { + return Err(DoryAssistVerifierError::StageOutputMismatch { + stage: DoryAssistStage::Stage1, + reason: format!( + "relation {:?} sumcheck final claim {:?} did not match semantic output claim {:?}", + relation.id, reduction.value, expected_output_claim + ), + }); + } + verify_relation_consistency(inputs.claims, semantic_relation, &relation_challenges)?; + + let opening_claims = relation_opening_claims(inputs.claims, semantic_relation)?; + append_opening_claims(&opening_claims, transcript); + relation_outputs.push(Stage1RelationOutput { + id: relation.id, + relation_challenges, + input_claim, + sumcheck_point: reduction.point, + sumcheck_final_claim: reduction.value, + expected_output_claim, + opening_claims, + }); + } + let challenge = squeeze_fq_challenge(transcript, b"dory_stage1_challenge"); + + Ok(Stage1Output { + relation_count: inputs.proof.relation_count(), + relation_outputs, + challenge, + }) +} + +fn absorb_relation(relation: &Stage1RelationProof, transcript: &mut T) +where + T: Transcript::Field>, +{ + transcript.append(&Label(b"stage1_relation_id")); + transcript.append(&U64Word(relation_transcript_tag(relation) as u64)); + transcript.append(&Label(b"stage1_sumcheck_domain")); + transcript.append(&U64Word(0)); + transcript.append(&Label(b"stage1_sumcheck_rounds")); + transcript.append(&U64Word(relation.sumcheck.rounds as u64)); + transcript.append(&Label(b"stage1_sumcheck_degree")); + transcript.append(&U64Word(relation.sumcheck.degree as u64)); +} + +fn verify_relation_sumcheck( + relation: &Stage1RelationProof, + input_claim: Fq, + transcript: &mut T, +) -> Result, DoryAssistVerifierError> +where + T: Transcript::Field>, +{ + if !matches!( + relation.sumcheck.domain, + DoryAssistSumcheckDomain::BooleanHypercube + ) { + return Err(DoryAssistVerifierError::StageClaimMismatch { + stage: DoryAssistStage::Stage1, + reason: format!( + "relation {:?} must use the Boolean hypercube domain", + relation.id + ), + }); + } + if relation.sumcheck.degree == 0 { + return Err(DoryAssistVerifierError::InvalidProofShape { + component: "stage1.relation.sumcheck.degree", + reason: "sumcheck degree must be nonzero".to_string(), + }); + } + + let claim = SumcheckClaim { + num_vars: relation.sumcheck.rounds, + degree: relation.sumcheck.degree, + claimed_sum: input_claim, + }; + + if relation.sumcheck_proof.round_polynomials.len() != claim.num_vars { + return Err(stage_sumcheck_failed( + relation, + format!( + "expected {} rounds, proof contains {}", + claim.num_vars, + relation.sumcheck_proof.round_polynomials.len() + ), + )); + } + + let mut running_sum = claim.claimed_sum; + let mut challenges = Vec::with_capacity(claim.num_vars); + for (round, round_proof) in relation.sumcheck_proof.round_polynomials.iter().enumerate() { + if round_proof.degree() > claim.degree { + return Err(stage_sumcheck_failed( + relation, + format!( + "degree bound exceeded: degree {}, max {}", + round_proof.degree(), + claim.degree + ), + )); + } + + let coeffs = round_proof.coeffs_except_linear_term(); + if coeffs.is_empty() { + return Err(stage_sumcheck_failed( + relation, + format!( + "round {round}: compressed round polynomial requires >= 2 coefficients, got 0" + ), + )); + } + + transcript.append(&LabelWithCount( + SUMCHECK_ROUND_TRANSCRIPT_LABEL, + coeffs.len() as u64, + )); + for coeff in coeffs { + transcript.append(coeff); + } + + let challenge = squeeze_fq(transcript); + running_sum = round_proof.evaluate_with_hint(running_sum, challenge); + challenges.push(challenge); + } + + Ok(EvaluationClaim::new(challenges, running_sum)) +} + +fn sample_relation_challenges( + relation: &DoryAssistRelationClaims, + transcript: &mut T, +) -> Vec +where + T: Transcript::Field>, +{ + relation + .required_challenges() + .into_iter() + .map(|id| DoryAssistChallengeValue { + id, + value: squeeze_fq(transcript), + }) + .collect() +} + +fn verify_relation_consistency( + claims: &DoryAssistProofClaims, + relation: &DoryAssistRelationClaims, + relation_challenges: &[DoryAssistChallengeValue], +) -> Result<(), DoryAssistVerifierError> { + for (index, consistency) in relation.consistency.iter().enumerate() { + let ConsistencyClaim::EqualExpressions { left, right } = consistency; + let left_value = evaluate_relation_expression(left, claims, relation_challenges)?; + let right_value = evaluate_relation_expression(right, claims, relation_challenges)?; + + if left_value != right_value { + return Err(DoryAssistVerifierError::StageOutputMismatch { + stage: DoryAssistStage::Stage1, + reason: format!( + "relation {:?} consistency claim {index} evaluated unequal expressions: left {left_value:?}, right {right_value:?}", + relation.id + ), + }); + } + } + + Ok(()) +} + +fn evaluate_relation_expression( + expression: &DoryAssistExpr, + claims: &DoryAssistProofClaims, + relation_challenges: &[DoryAssistChallengeValue], +) -> Result { + expression.try_evaluate( + |id| resolve_stage1_opening_claim(claims, id), + |id| resolve_relation_challenge(relation_challenges, id), + |id| resolve_stage1_public_claim(claims, id), + ) +} + +fn resolve_stage1_opening_claim( + claims: &DoryAssistProofClaims, + id: &DoryAssistOpeningId, +) -> Result { + claims + .stage1 + .opening_claim(id) + .ok_or(DoryAssistVerifierError::MissingOpeningClaim { id: *id }) +} + +fn resolve_relation_challenge( + challenges: &[DoryAssistChallengeValue], + id: &DoryAssistChallengeId, +) -> Result { + challenges + .iter() + .find(|challenge| challenge.id == *id) + .map(|challenge| challenge.value) + .ok_or(DoryAssistVerifierError::MissingStageClaimChallenge { id: *id }) +} + +fn resolve_stage1_public_claim( + claims: &DoryAssistProofClaims, + id: &DoryAssistPublicId, +) -> Result { + claims + .stage1 + .public_claim(id) + .ok_or(DoryAssistVerifierError::MissingStageClaimPublic { id: *id }) +} + +fn relation_opening_claims( + claims: &DoryAssistProofClaims, + relation: &DoryAssistRelationClaims, +) -> Result, DoryAssistVerifierError> { + relation + .required_openings() + .into_iter() + .map(|id| { + Ok(DoryAssistOpeningClaim { + id, + value: resolve_stage1_opening_claim(claims, &id)?, + }) + }) + .collect() +} + +fn append_opening_claims(opening_claims: &[DoryAssistOpeningClaim], transcript: &mut T) +where + T: Transcript::Field>, +{ + for opening_claim in opening_claims { + transcript.append_labeled(b"opening_claim", &opening_claim.value); + } +} + +fn stage_sumcheck_failed( + relation: &Stage1RelationProof, + reason: String, +) -> DoryAssistVerifierError { + DoryAssistVerifierError::StageSumcheckFailed { + stage: DoryAssistStage::Stage1, + relation: relation.id, + reason, + } +} + +#[expect( + clippy::expect_used, + reason = "stage 1 relation IDs are a subset of the canonical protocol catalog" +)] +fn canonical_stage1_relation_claims( + dimensions: jolt_claims::protocols::dory_assist::DoryAssistDimensions, +) -> Vec> { + let protocol = protocol_claims::(dimensions); + canonical_stage1_relation_specs(dimensions) + .iter() + .map(|spec| { + protocol + .relation(spec.id) + .expect("stage 1 relation belongs to canonical Dory-assist protocol") + .clone() + }) + .collect() +} + +#[expect( + clippy::expect_used, + reason = "verified stage 1 relations are drawn from the canonical Dory-assist catalog" +)] +fn relation_transcript_tag(relation: &Stage1RelationProof) -> usize { + CANONICAL_RELATION_ORDER + .iter() + .position(|id| *id == relation.id) + .expect("stage 1 relation has a canonical transcript tag") +} + +#[cfg(test)] +mod tests { + use super::*; + use jolt_claims::protocols::dory_assist::{ + formulas::gt, DoryAssistConsistencyClaim, DoryAssistRelationId, DoryAssistSumcheckSpec, + }; + use jolt_claims::{opening, public}; + use jolt_field::FromPrimitiveInt; + + #[test] + fn consistency_claim_accepts_equal_opening_values() { + let relation = synthetic_consistency_relation(); + let claims = DoryAssistProofClaims::default(); + + assert_eq!(verify_relation_consistency(&claims, &relation, &[]), Ok(())); + } + + #[test] + fn consistency_claim_rejects_unequal_opening_values() { + let relation = synthetic_consistency_relation(); + let mut claims = DoryAssistProofClaims::default(); + claims.stage1.gt_exponentiation.shifted_accumulator = Fq::from_u64(1); + + assert!(matches!( + verify_relation_consistency(&claims, &relation, &[]), + Err(DoryAssistVerifierError::StageOutputMismatch { + stage: DoryAssistStage::Stage1, + .. + }) + )); + } + + #[test] + fn consistency_claim_can_use_public_terms() { + let left = + opening(gt::exp_accumulator_opening()) + public(DoryAssistPublicId::GtShiftEqKernel); + let right = opening(gt::exp_shifted_accumulator_opening()) + + public(DoryAssistPublicId::GtShiftEqKernel); + let relation = DoryAssistRelationClaims::new( + DoryAssistRelationId::GtExponentiation, + DoryAssistSumcheckSpec::boolean(1, 1), + DoryAssistExpr::zero(), + DoryAssistExpr::zero(), + ) + .with_consistency([DoryAssistConsistencyClaim::equal_expressions(left, right)]); + + assert_eq!( + verify_relation_consistency(&DoryAssistProofClaims::default(), &relation, &[]), + Ok(()) + ); + } + + fn synthetic_consistency_relation() -> DoryAssistRelationClaims { + DoryAssistRelationClaims::new( + DoryAssistRelationId::GtExponentiation, + DoryAssistSumcheckSpec::boolean(1, 1), + DoryAssistExpr::zero(), + DoryAssistExpr::zero(), + ) + .with_consistency([DoryAssistConsistencyClaim::same_evaluation( + gt::exp_accumulator_opening(), + gt::exp_shifted_accumulator_opening(), + )]) + } +} diff --git a/crates/jolt-dory-assist-verifier/src/stages/stage2/inputs.rs b/crates/jolt-dory-assist-verifier/src/stages/stage2/inputs.rs new file mode 100644 index 0000000000..2ad670e83d --- /dev/null +++ b/crates/jolt-dory-assist-verifier/src/stages/stage2/inputs.rs @@ -0,0 +1,128 @@ +//! Typed inputs consumed by stage 2. + +use jolt_claims::protocols::dory_assist::{ + formulas::{composition, dory_reduce}, + DoryAssistCopyConstraint, DoryAssistDimensions, +}; +use serde::{Deserialize, Serialize}; + +use crate::{proof::DoryAssistProofClaims, stages::stage1::Stage1Output, verifier::CheckedInputs}; + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct Stage2Proof { + pub copy_constraints: Vec, +} + +impl Stage2Proof { + pub fn canonical_for_dimensions(dimensions: DoryAssistDimensions) -> Self { + Self { + copy_constraints: canonical_stage2_copy_constraints(dimensions), + } + } + + pub fn relation_count(&self) -> u32 { + self.copy_constraints.len() as u32 + } +} + +#[derive(Clone, Copy)] +pub struct Stage2Inputs<'a, 'p> { + pub checked: &'a CheckedInputs<'p>, + pub dimensions: DoryAssistDimensions, + pub proof: &'a Stage2Proof, + pub claims: &'a DoryAssistProofClaims, + pub stage1: &'a Stage1Output, +} + +pub(crate) fn canonical_stage2_copy_constraints( + dimensions: DoryAssistDimensions, +) -> Vec { + let dory_reduce_copies = if dimensions.dory_reduce.reduce_rounds() == 1 { + dory_reduce::initial_state_copy_constraints() + .into_iter() + .chain(dory_reduce::proof_artifact_copy_constraints(0)) + .chain(dory_reduce::round_setup_artifact_copy_constraints( + dimensions.dory_reduce.reduce_rounds(), + 0, + )) + .chain(dory_reduce::transition_transcript_scalar_copy_constraints( + dimensions.dory_reduce.point_len(), + 0, + )) + .chain(dory_reduce::scalar_fold_transcript_scalar_copy_constraints( + dimensions.dory_reduce.point_len(), + 0, + )) + .collect() + } else { + Vec::new() + }; + + composition::public_input_copy_constraints() + .into_iter() + .chain(composition::gt_copy_constraints()) + .chain(composition::g1_copy_constraints()) + .chain(composition::g2_copy_constraints()) + .chain(composition::miller_loop_copy_constraints()) + .chain(dory_reduce_copies) + .collect() +} + +#[cfg(test)] +mod tests { + #![expect( + clippy::expect_used, + reason = "tests fail loudly on invalid fixture dimensions" + )] + + use super::*; + use jolt_claims::protocols::dory_assist::{ + DoryAssistRelationId, DoryAssistValueRef, DoryReduceDimensions, G1Dimensions, G2Dimensions, + GtDimensions, MillerLoopDimensions, PrefixPackingDimensions, WiringDimensions, + }; + + fn dimensions(reduce_rounds: usize) -> DoryAssistDimensions { + DoryAssistDimensions::new( + GtDimensions::new(7, 2, 3), + G1Dimensions::new(8, 2, 3), + G2Dimensions::new(8, 2, 3), + MillerLoopDimensions::new(7, 2, 8), + DoryReduceDimensions::new(2 * reduce_rounds, reduce_rounds), + WiringDimensions::new(6), + PrefixPackingDimensions::new(0, 0, 0).expect("valid empty packing dimensions"), + ) + } + + #[test] + fn singleton_stage2_copy_catalog_keeps_direct_dory_reduce_copies() { + let constraints = canonical_stage2_copy_constraints(dimensions(1)); + + assert!(constraints.iter().any(has_dory_reduce_endpoint)); + } + + #[test] + fn multiround_stage2_copy_catalog_does_not_use_direct_dory_reduce_row_chains() { + let constraints = canonical_stage2_copy_constraints(dimensions(2)); + + assert!(!constraints.iter().any(has_dory_reduce_endpoint)); + } + + fn has_dory_reduce_endpoint(constraint: &DoryAssistCopyConstraint) -> bool { + [constraint.source, constraint.target] + .into_iter() + .any(|endpoint| { + matches!( + endpoint, + DoryAssistValueRef::Witness { + relation: DoryAssistRelationId::DoryReduceGtTransition + | DoryAssistRelationId::DoryReduceG1Transition + | DoryAssistRelationId::DoryReduceG2Transition + | DoryAssistRelationId::DoryReduceScalarFold + | DoryAssistRelationId::DoryReduceStateChain + | DoryAssistRelationId::DoryReduceBoundary, + .. + } + ) + }) + } +} diff --git a/crates/jolt-dory-assist-verifier/src/stages/stage2/mod.rs b/crates/jolt-dory-assist-verifier/src/stages/stage2/mod.rs new file mode 100644 index 0000000000..1776a342e4 --- /dev/null +++ b/crates/jolt-dory-assist-verifier/src/stages/stage2/mod.rs @@ -0,0 +1,9 @@ +//! Stage 2 copy-constraint verifier. + +pub mod inputs; +pub mod outputs; +mod verify; + +pub use inputs::{Stage2Inputs, Stage2Proof}; +pub use outputs::Stage2Output; +pub use verify::verify; diff --git a/crates/jolt-dory-assist-verifier/src/stages/stage2/outputs.rs b/crates/jolt-dory-assist-verifier/src/stages/stage2/outputs.rs new file mode 100644 index 0000000000..7de62d1a8c --- /dev/null +++ b/crates/jolt-dory-assist-verifier/src/stages/stage2/outputs.rs @@ -0,0 +1,28 @@ +//! Typed outputs produced by stage 2 verification. + +use jolt_claims::protocols::dory_assist::{ + formulas::dory_reduce::DoryReducePublicFoldConstraint, DoryAssistCopyConstraint, +}; +use jolt_field::Fq; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage2Output { + pub relation_count: u32, + pub copy_constraints: Vec, + pub dory_reduce_public_folds: Vec, + pub challenge: Fq, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage2CopyConstraintOutput { + pub constraint: DoryAssistCopyConstraint, + pub source_value: Fq, + pub target_value: Fq, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage2DoryReducePublicFoldOutput { + pub constraint: DoryReducePublicFoldConstraint, + pub expected_value: Fq, + pub target_value: Fq, +} diff --git a/crates/jolt-dory-assist-verifier/src/stages/stage2/verify.rs b/crates/jolt-dory-assist-verifier/src/stages/stage2/verify.rs new file mode 100644 index 0000000000..50887cf41c --- /dev/null +++ b/crates/jolt-dory-assist-verifier/src/stages/stage2/verify.rs @@ -0,0 +1,389 @@ +use jolt_claims::protocols::dory_assist::{ + formulas::dory_reduce::DoryReducePublicFoldConstraint, DoryAssistCopyConstraint, + DoryAssistOpeningId, DoryAssistRelationId, DoryAssistValueRef, DoryAssistVirtualPolynomial, +}; +use jolt_dory::DoryScheme; +use jolt_field::{Fq, FromPrimitiveInt}; +use jolt_openings::CommitmentScheme; +use jolt_poly::EqPolynomial; +use jolt_transcript::{Label, Transcript, U64Word}; + +use super::{ + inputs::{canonical_stage2_copy_constraints, Stage2Inputs}, + outputs::{Stage2CopyConstraintOutput, Stage2DoryReducePublicFoldOutput, Stage2Output}, +}; +use crate::{ + proof::DoryAssistProofClaims, + stages::stage1::{outputs::Stage1RelationOutput, Stage1Output}, + verifier::squeeze_fq_challenge, + DoryAssistStage, DoryAssistVerifierError, +}; + +pub fn verify( + inputs: Stage2Inputs<'_, '_>, + transcript: &mut T, +) -> Result +where + T: Transcript::Field>, +{ + if inputs.proof.copy_constraints.is_empty() { + return Err(DoryAssistVerifierError::InvalidProofShape { + component: "stage2.copy_constraints", + reason: "copy_constraints must be nonempty".to_string(), + }); + } + + let expected_constraints = canonical_stage2_copy_constraints(inputs.dimensions); + if inputs.proof.copy_constraints != expected_constraints { + return Err(DoryAssistVerifierError::StageClaimMismatch { + stage: DoryAssistStage::Stage2, + reason: format!( + "stage 2 copy constraints must match canonical Dory-assist direct-copy stencil: expected {expected_constraints:?}, got {:?}", + inputs.proof.copy_constraints + ), + }); + } + + transcript.append(&Label(b"dory_assist_stage2")); + transcript.append(&Label(inputs.checked.mode_name().as_bytes())); + transcript.append(&Label(b"stage1_relations")); + transcript.append(&U64Word(inputs.stage1.relation_count as u64)); + transcript.append(&Label(b"stage2_copy_constraints")); + transcript.append(&U64Word(inputs.proof.relation_count() as u64)); + + let mut copy_constraints = Vec::with_capacity(inputs.proof.copy_constraints.len()); + for (index, constraint) in inputs.proof.copy_constraints.iter().enumerate() { + transcript.append(&Label(b"stage2_copy_constraint")); + transcript.append(&U64Word(index as u64)); + let source_value = resolve_copy_value(inputs.claims, inputs.stage1, constraint.source)?; + let target_value = resolve_copy_value(inputs.claims, inputs.stage1, constraint.target)?; + transcript.append(&Label(b"stage2_copy_source")); + transcript.append(&source_value); + transcript.append(&Label(b"stage2_copy_target")); + transcript.append(&target_value); + + if source_value != target_value { + return Err(copy_constraint_mismatch( + constraint, + source_value, + target_value, + )); + } + + copy_constraints.push(Stage2CopyConstraintOutput { + constraint: *constraint, + source_value, + target_value, + }); + } + + let public_folds = verify_dory_reduce_public_folds(&inputs, transcript)?; + let challenge = squeeze_fq_challenge(transcript, b"dory_stage2_challenge"); + + Ok(Stage2Output { + relation_count: inputs.proof.relation_count(), + copy_constraints, + dory_reduce_public_folds: public_folds, + challenge, + }) +} + +fn verify_dory_reduce_public_folds( + inputs: &Stage2Inputs<'_, '_>, + transcript: &mut T, +) -> Result, DoryAssistVerifierError> +where + T: Transcript::Field>, +{ + let constraints = + jolt_claims::protocols::dory_assist::formulas::dory_reduce::public_fold_constraints( + inputs.dimensions.dory_reduce, + ); + transcript.append(&Label(b"stage2_dory_reduce_public_folds")); + transcript.append(&U64Word(constraints.len() as u64)); + + let mut outputs = Vec::with_capacity(constraints.len()); + for (index, constraint) in constraints.into_iter().enumerate() { + transcript.append(&Label(b"stage2_dory_reduce_public_fold")); + transcript.append(&U64Word(index as u64)); + + let expected_value = evaluate_public_fold(inputs.claims, inputs.stage1, &constraint)?; + let target_value = resolve_copy_value(inputs.claims, inputs.stage1, constraint.target)?; + transcript.append(&Label(b"s2_dory_reduce_fold_expected")); + transcript.append(&expected_value); + transcript.append(&Label(b"s2_dory_reduce_fold_target")); + transcript.append(&target_value); + + if expected_value != target_value { + return Err(DoryAssistVerifierError::StageOutputMismatch { + stage: DoryAssistStage::Stage2, + reason: format!( + "Dory-reduce public fold {constraint:?} resolved to unequal values: expected {expected_value:?}, target {target_value:?}" + ), + }); + } + + outputs.push(Stage2DoryReducePublicFoldOutput { + constraint, + expected_value, + target_value, + }); + } + + Ok(outputs) +} + +fn evaluate_public_fold( + claims: &DoryAssistProofClaims, + stage1: &Stage1Output, + constraint: &DoryReducePublicFoldConstraint, +) -> Result { + let opening = constraint.target.witness_opening().ok_or_else(|| { + DoryAssistVerifierError::StageClaimMismatch { + stage: DoryAssistStage::Stage2, + reason: format!( + "Dory-reduce public fold target has no witness opening: {:?}", + constraint.target + ), + } + })?; + ensure_stage1_recorded_opening(stage1, opening)?; + + let relation = relation_output_for_opening(stage1, opening)?; + let weights = EqPolynomial::new(relation.sumcheck_point.as_slice().to_vec()).evaluations(); + if constraint.sources.len() > weights.len() { + return Err(DoryAssistVerifierError::InvalidProofShape { + component: "stage2.dory_reduce_public_fold.sources", + reason: format!( + "public fold has {} sources but the relation point only supports {} rows", + constraint.sources.len(), + weights.len() + ), + }); + } + + constraint + .sources + .iter() + .zip(weights) + .try_fold(Fq::default(), |acc, (id, weight)| { + let value = claims + .stage1 + .public_claim(id) + .ok_or(DoryAssistVerifierError::MissingStageClaimPublic { id: *id })?; + Ok(acc + value * weight) + }) +} + +fn resolve_copy_value( + claims: &DoryAssistProofClaims, + stage1: &Stage1Output, + value_ref: DoryAssistValueRef, +) -> Result { + match value_ref { + DoryAssistValueRef::Witness { .. } => { + let opening = value_ref.witness_opening().ok_or_else(|| { + DoryAssistVerifierError::StageClaimMismatch { + stage: DoryAssistStage::Stage2, + reason: format!("copy witness endpoint has no opening: {value_ref:?}"), + } + })?; + ensure_stage1_recorded_opening(stage1, opening)?; + resolve_witness_value(claims, value_ref, opening) + } + DoryAssistValueRef::Public { id, .. } => claims + .stage1 + .public_claim(&id) + .ok_or(DoryAssistVerifierError::MissingStageClaimPublic { id }), + DoryAssistValueRef::Constant(value) => Ok(Fq::from_u64(value as u64)), + DoryAssistValueRef::Challenge(id) => Err(DoryAssistVerifierError::StageClaimMismatch { + stage: DoryAssistStage::Stage2, + reason: format!("copy constraints cannot directly reference challenge {id:?}"), + }), + } +} + +fn resolve_witness_value( + claims: &DoryAssistProofClaims, + value_ref: DoryAssistValueRef, + opening: DoryAssistOpeningId, +) -> Result { + match value_ref { + DoryAssistValueRef::Witness { + relation: DoryAssistRelationId::GtMultiplication, + polynomial: DoryAssistVirtualPolynomial::Gt(polynomial), + row, + component, + .. + } => claims + .stage1 + .gt_multiplication + .row_claim(row, component, polynomial) + .ok_or(DoryAssistVerifierError::MissingOpeningClaim { id: opening }), + DoryAssistValueRef::Witness { .. } => claims + .stage1 + .opening_claim(&opening) + .ok_or(DoryAssistVerifierError::MissingOpeningClaim { id: opening }), + DoryAssistValueRef::Public { .. } + | DoryAssistValueRef::Challenge(_) + | DoryAssistValueRef::Constant(_) => Err(DoryAssistVerifierError::StageClaimMismatch { + stage: DoryAssistStage::Stage2, + reason: format!("copy witness resolver received non-witness endpoint {value_ref:?}"), + }), + } +} + +fn ensure_stage1_recorded_opening( + stage1: &Stage1Output, + opening: DoryAssistOpeningId, +) -> Result<(), DoryAssistVerifierError> { + let recorded = stage1 + .relation_outputs + .iter() + .flat_map(|relation| &relation.opening_claims) + .any(|claim| claim.id == opening); + + if recorded { + Ok(()) + } else { + Err(DoryAssistVerifierError::StageClaimMismatch { + stage: DoryAssistStage::Stage2, + reason: format!("copy endpoint opening {opening:?} was not verified by stage 1"), + }) + } +} + +fn relation_output_for_opening( + stage1: &Stage1Output, + opening: DoryAssistOpeningId, +) -> Result<&Stage1RelationOutput, DoryAssistVerifierError> { + stage1 + .relation_outputs + .iter() + .find(|relation| { + relation + .opening_claims + .iter() + .any(|claim| claim.id == opening) + }) + .ok_or(DoryAssistVerifierError::StageClaimMismatch { + stage: DoryAssistStage::Stage2, + reason: format!("copy endpoint opening {opening:?} was not verified by stage 1"), + }) +} + +fn copy_constraint_mismatch( + constraint: &DoryAssistCopyConstraint, + source_value: Fq, + target_value: Fq, +) -> DoryAssistVerifierError { + DoryAssistVerifierError::StageOutputMismatch { + stage: DoryAssistStage::Stage2, + reason: format!( + "copy constraint {constraint:?} resolved to unequal values: source {source_value:?}, target {target_value:?}" + ), + } +} + +#[cfg(test)] +mod tests { + #![expect( + clippy::expect_used, + reason = "tests fail loudly on malformed local fixtures" + )] + + use super::*; + use crate::proof::DoryAssistOpeningClaim; + use jolt_claims::protocols::dory_assist::{DoryAssistPublicId, DoryReducePolynomial}; + use jolt_field::FromPrimitiveInt; + use jolt_poly::{Point, HIGH_TO_LOW}; + + #[test] + fn public_fold_evaluates_round_indexed_sources_at_relation_point() { + let target = DoryAssistValueRef::witness( + DoryAssistRelationId::DoryReduceGtTransition, + DoryAssistVirtualPolynomial::DoryReduce(DoryReducePolynomial::Beta), + 0, + 0, + ); + let constraint = DoryReducePublicFoldConstraint::new( + jolt_claims::protocols::dory_assist::DoryAssistValueType::Scalar, + vec![ + DoryAssistPublicId::TranscriptScalar(0), + DoryAssistPublicId::TranscriptScalar(1), + ], + target, + ); + let mut claims = DoryAssistProofClaims::default(); + claims.stage1.public.input.transcript_scalars = vec![Fq::from_u64(3), Fq::from_u64(5)]; + let point = Fq::from_u64(7); + let expected = Fq::from_u64(3) * (Fq::from_u64(1) - point) + Fq::from_u64(5) * point; + let opening = target.witness_opening().expect("target opening"); + let stage1 = Stage1Output { + relation_count: 1, + relation_outputs: vec![Stage1RelationOutput { + id: DoryAssistRelationId::DoryReduceGtTransition, + relation_challenges: Vec::new(), + input_claim: Fq::default(), + sumcheck_point: Point::::high_to_low(vec![point]), + sumcheck_final_claim: Fq::default(), + expected_output_claim: Fq::default(), + opening_claims: vec![DoryAssistOpeningClaim { + id: opening, + value: expected, + }], + }], + challenge: Fq::default(), + }; + + let actual = + evaluate_public_fold(&claims, &stage1, &constraint).expect("public fold evaluates"); + + assert_eq!(actual, expected); + } + + #[test] + fn public_fold_rejects_sources_larger_than_relation_domain() { + let target = DoryAssistValueRef::witness( + DoryAssistRelationId::DoryReduceGtTransition, + DoryAssistVirtualPolynomial::DoryReduce(DoryReducePolynomial::Beta), + 0, + 0, + ); + let constraint = DoryReducePublicFoldConstraint::new( + jolt_claims::protocols::dory_assist::DoryAssistValueType::Scalar, + vec![ + DoryAssistPublicId::TranscriptScalar(0), + DoryAssistPublicId::TranscriptScalar(1), + ], + target, + ); + let opening = target.witness_opening().expect("target opening"); + let stage1 = Stage1Output { + relation_count: 1, + relation_outputs: vec![Stage1RelationOutput { + id: DoryAssistRelationId::DoryReduceGtTransition, + relation_challenges: Vec::new(), + input_claim: Fq::default(), + sumcheck_point: Point::::default(), + sumcheck_final_claim: Fq::default(), + expected_output_claim: Fq::default(), + opening_claims: vec![DoryAssistOpeningClaim { + id: opening, + value: Fq::default(), + }], + }], + challenge: Fq::default(), + }; + + let result = evaluate_public_fold(&DoryAssistProofClaims::default(), &stage1, &constraint); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::InvalidProofShape { + component: "stage2.dory_reduce_public_fold.sources", + .. + }) + )); + } +} diff --git a/crates/jolt-dory-assist-verifier/src/stages/stage3/inputs.rs b/crates/jolt-dory-assist-verifier/src/stages/stage3/inputs.rs new file mode 100644 index 0000000000..aa42a17ea5 --- /dev/null +++ b/crates/jolt-dory-assist-verifier/src/stages/stage3/inputs.rs @@ -0,0 +1,31 @@ +//! Typed inputs consumed by stage 3. + +use jolt_claims::protocols::dory_assist::DoryAssistOpeningId; +use jolt_crypto::GrumpkinPoint; +use jolt_field::Fq; +use jolt_hyrax::{HyraxCommitment, HyraxOpeningProof}; +use serde::{Deserialize, Serialize}; + +use crate::{ + proof::{DoryAssistProofClaims, DoryAssistPublicOutputs}, + stages::{stage1::Stage1Output, stage2::Stage2Output}, + verifier::CheckedInputs, +}; + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct Stage3Proof { + pub packed_eval: Fq, + pub reduced_openings: Vec, +} + +#[derive(Clone, Copy)] +pub struct Stage3Inputs<'a, 'p> { + pub checked: &'a CheckedInputs<'p>, + pub proof: &'a Stage3Proof, + pub opening_proof: &'a HyraxOpeningProof, + pub claims: &'a DoryAssistProofClaims, + pub dense_commitment: &'a HyraxCommitment, + pub public_outputs: &'a DoryAssistPublicOutputs, + pub stage1: &'a Stage1Output, + pub stage2: &'a Stage2Output, +} diff --git a/crates/jolt-dory-assist-verifier/src/stages/stage3/mod.rs b/crates/jolt-dory-assist-verifier/src/stages/stage3/mod.rs new file mode 100644 index 0000000000..85b0734059 --- /dev/null +++ b/crates/jolt-dory-assist-verifier/src/stages/stage3/mod.rs @@ -0,0 +1,9 @@ +//! Stage 3 packed Hyrax opening verifier. + +pub mod inputs; +pub mod outputs; +mod verify; + +pub use inputs::{Stage3Inputs, Stage3Proof}; +pub use outputs::Stage3Output; +pub use verify::verify; diff --git a/crates/jolt-dory-assist-verifier/src/stages/stage3/outputs.rs b/crates/jolt-dory-assist-verifier/src/stages/stage3/outputs.rs new file mode 100644 index 0000000000..b58276ec76 --- /dev/null +++ b/crates/jolt-dory-assist-verifier/src/stages/stage3/outputs.rs @@ -0,0 +1,12 @@ +//! Typed outputs produced by stage 3 verification. + +use crate::proof::DoryAssistOpeningClaim; +use jolt_field::Fq; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Stage3Output { + pub packed_eval: Fq, + pub reduced_claims: Vec, + pub expected_packed_eval: Fq, + pub challenge: Fq, +} diff --git a/crates/jolt-dory-assist-verifier/src/stages/stage3/verify.rs b/crates/jolt-dory-assist-verifier/src/stages/stage3/verify.rs new file mode 100644 index 0000000000..7558043ef8 --- /dev/null +++ b/crates/jolt-dory-assist-verifier/src/stages/stage3/verify.rs @@ -0,0 +1,217 @@ +use jolt_claims::protocols::dory_assist::DoryAssistOpeningId; +use jolt_dory::DoryScheme; +use jolt_field::Fq; +use jolt_hyrax::HyraxDimensions; +use jolt_openings::CommitmentScheme; +use jolt_poly::EqPolynomial; +use jolt_transcript::{Label, LabelWithCount, Transcript, U64Word}; + +use super::{inputs::Stage3Inputs, outputs::Stage3Output}; +use crate::{ + derive_hyrax_verifier_setup, proof::DoryAssistOpeningClaim, verifier::squeeze_fq_challenge, + DoryAssistHyrax, DoryAssistStage, DoryAssistVerifierError, +}; + +pub fn verify( + inputs: Stage3Inputs<'_, '_>, + transcript: &mut T, +) -> Result +where + T: Transcript::Field>, +{ + if inputs.opening_proof.combined_row.is_empty() { + return Err(DoryAssistVerifierError::InvalidProofShape { + component: "stage3.opening_proof.combined_row", + reason: "combined_row must be nonempty".to_string(), + }); + } + if inputs.claims.opening.packed_point.is_empty() { + return Err(DoryAssistVerifierError::InvalidProofShape { + component: "stage3.claims.opening.packed_point", + reason: "packed_point must be nonempty".to_string(), + }); + } + if inputs.dense_commitment.rows.is_empty() { + return Err(DoryAssistVerifierError::InvalidProofShape { + component: "stage3.dense_commitment.rows", + reason: "dense commitment must contain at least one row".to_string(), + }); + } + if inputs.proof.reduced_openings.is_empty() { + return Err(DoryAssistVerifierError::InvalidProofShape { + component: "stage3.reduced_openings", + reason: "reduced_openings must be nonempty".to_string(), + }); + } + if inputs.proof.packed_eval != inputs.claims.opening.packed_eval { + return Err(DoryAssistVerifierError::StageOutputMismatch { + stage: DoryAssistStage::Stage3, + reason: "stage packed_eval must match opening claim packed_eval".to_string(), + }); + } + + let expected_openings = canonical_reduced_openings(inputs); + if inputs.proof.reduced_openings != expected_openings { + return Err(DoryAssistVerifierError::StageClaimMismatch { + stage: DoryAssistStage::Stage3, + reason: format!( + "stage 3 reduced openings must match the canonical verified Stage 1 opening order: expected {expected_openings:?}, got {:?}", + inputs.proof.reduced_openings + ), + }); + } + + let dimensions = infer_hyrax_dimensions( + inputs.dense_commitment.rows.len(), + inputs.opening_proof.combined_row.len(), + inputs.claims.opening.packed_point.len(), + )?; + let reduced_claims = resolve_reduced_claims(inputs, &expected_openings)?; + let expected_packed_eval = + evaluate_packed_claim(&inputs.claims.opening.packed_point, &reduced_claims)?; + if inputs.proof.packed_eval != expected_packed_eval { + return Err(DoryAssistVerifierError::StageOutputMismatch { + stage: DoryAssistStage::Stage3, + reason: "packed eval must equal the prefix-weighted fold of reduced opening claims" + .to_string(), + }); + } + + absorb_stage3_inputs(&inputs, transcript); + let challenge = squeeze_fq_challenge(transcript, b"dory_stage3_challenge"); + + let hyrax_setup = derive_hyrax_verifier_setup(dimensions)?; + DoryAssistHyrax::verify_opening_proof( + &hyrax_setup, + inputs.dense_commitment, + &inputs.claims.opening.packed_point, + inputs.claims.opening.packed_eval, + inputs.opening_proof, + )?; + + Ok(Stage3Output { + packed_eval: inputs.proof.packed_eval, + reduced_claims, + expected_packed_eval, + challenge, + }) +} + +fn canonical_reduced_openings(inputs: Stage3Inputs<'_, '_>) -> Vec { + let mut openings = Vec::new(); + for opening_claim in inputs + .stage1 + .relation_outputs + .iter() + .flat_map(|relation| &relation.opening_claims) + { + if !openings.contains(&opening_claim.id) { + openings.push(opening_claim.id); + } + } + openings +} + +fn resolve_reduced_claims( + inputs: Stage3Inputs<'_, '_>, + openings: &[DoryAssistOpeningId], +) -> Result, DoryAssistVerifierError> { + openings + .iter() + .map(|opening| { + let value = inputs + .stage1 + .relation_outputs + .iter() + .flat_map(|relation| &relation.opening_claims) + .find(|claim| claim.id == *opening) + .map(|claim| claim.value) + .ok_or(DoryAssistVerifierError::MissingOpeningClaim { id: *opening })?; + Ok(DoryAssistOpeningClaim { + id: *opening, + value, + }) + }) + .collect() +} + +fn evaluate_packed_claim( + packed_point: &[Fq], + reduced_claims: &[DoryAssistOpeningClaim], +) -> Result { + let weights = EqPolynomial::new(packed_point.to_vec()).evaluations(); + if reduced_claims.len() > weights.len() { + return Err(DoryAssistVerifierError::InvalidProofShape { + component: "stage3.claims.opening.packed_point", + reason: format!( + "packed point has {} variables, which only supports {} reduced claims; proof needs {}", + packed_point.len(), + weights.len(), + reduced_claims.len() + ), + }); + } + + Ok(reduced_claims + .iter() + .zip(weights) + .fold(Fq::default(), |acc, (claim, weight)| { + acc + claim.value * weight + })) +} + +fn absorb_stage3_inputs(inputs: &Stage3Inputs<'_, '_>, transcript: &mut T) +where + T: Transcript::Field>, +{ + transcript.append(&Label(b"dory_assist_stage3")); + transcript.append(&Label(inputs.checked.mode_name().as_bytes())); + transcript.append(&Label(b"stage1_relations")); + transcript.append(&U64Word(inputs.stage1.relation_count as u64)); + transcript.append(&Label(b"stage2_relations")); + transcript.append(&U64Word(inputs.stage2.relation_count as u64)); + transcript.append(&Label(b"stage3_reduced_openings")); + transcript.append(&U64Word(inputs.proof.reduced_openings.len() as u64)); + transcript.append(&Label(b"stage3_packed_eval")); + transcript.append(&inputs.proof.packed_eval); + transcript.append(&LabelWithCount( + b"stage3_claim_point", + inputs.claims.opening.packed_point.len() as u64, + )); + for point_coordinate in &inputs.claims.opening.packed_point { + transcript.append(point_coordinate); + } + transcript.append(&Label(b"stage3_claim_eval")); + transcript.append(&inputs.claims.opening.packed_eval); + transcript.append(&Label(b"stage3_opening_proof")); + transcript.append(inputs.opening_proof); + transcript.append(&Label(b"stage3_dense_commitment")); + transcript.append(inputs.dense_commitment); + transcript.append(&Label(b"stage3_public_output")); + transcript.append(&inputs.public_outputs.pre_final_exponentiation); +} + +fn infer_hyrax_dimensions( + row_count: usize, + row_len: usize, + point_len: usize, +) -> Result { + let row_vars = checked_log2("stage3.dense_commitment.rows", row_count)?; + let col_vars = checked_log2("stage3.opening_proof.combined_row", row_len)?; + HyraxDimensions::new(point_len, row_vars, col_vars).map_err(|error| { + DoryAssistVerifierError::InvalidProofShape { + component: "stage3.hyrax_dimensions", + reason: error.to_string(), + } + }) +} + +fn checked_log2(component: &'static str, value: usize) -> Result { + if !value.is_power_of_two() { + return Err(DoryAssistVerifierError::InvalidProofShape { + component, + reason: format!("{value} is not a power of two"), + }); + } + Ok(value.trailing_zeros() as usize) +} diff --git a/crates/jolt-dory-assist-verifier/src/verifier.rs b/crates/jolt-dory-assist-verifier/src/verifier.rs new file mode 100644 index 0000000000..c835ef180c --- /dev/null +++ b/crates/jolt-dory-assist-verifier/src/verifier.rs @@ -0,0 +1,3800 @@ +//! Top-level Dory-assist verifier entry point. + +use jolt_claims::protocols::dory_assist::formulas::composition; +use jolt_crypto::{Bn254G1, Bn254G2, Bn254GT, JoltGroup}; +use jolt_dory::{ + DoryCommitment, DoryProof, DoryScheme, DoryVerifierSetup, DoryVerifierTranscriptScalars, +}; +use jolt_field::{CanonicalBytes, FixedByteSize, Fq, Fr, FromPrimitiveInt}; +use jolt_openings::{CommitmentScheme, ZkOpeningScheme}; +use jolt_transcript::{Label, LabelWithCount, Transcript}; +use jolt_verifier::{PcsAssistClearInput, PcsAssistZkInput, PcsProofAssist}; + +use crate::{ + artifacts::{ + DoryProofArtifactLayout, G1_ARTIFACT_COORDS, G2_ARTIFACT_COORDS, GT_ARTIFACT_COEFFS, + }, + config::DoryAssistConfig, + error::DoryAssistVerifierError, + native_final::{transparent_final_pairing_check, zk_final_pairing_check}, + proof::{default_dory_assist_dimensions, DoryAssistInputPublicClaims, DoryAssistProof}, + stages, +}; + +const MAX_DORY_ASSIST_OPENING_POINT_LEN: usize = 64; +const CHECKED_INPUT_DIGEST_LABEL: &[u8] = b"dory_assist_checked_input_digest"; +const VERIFIER_SETUP_DIGEST_LABEL: &[u8] = b"dory_assist_setup_digest"; +const DORY_PROOF_DIGEST_LABEL: &[u8] = b"dory_assist_proof_digest"; +const JOLT_COMMITMENT_DIGEST_LABEL: &[u8] = b"dory_assist_commitment_digest"; +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct DoryAssist; + +impl PcsProofAssist for DoryAssist { + type Proof = DoryAssistProof; + type Config = DoryAssistConfig; + type Error = DoryAssistVerifierError; + + fn selected_config() -> Self::Config { + DoryAssistConfig + } + + fn verify_clear( + config: &Self::Config, + input: PcsAssistClearInput<'_, DoryScheme>, + proof: &Self::Proof, + transcript: &mut T, + ) -> Result<(), Self::Error> + where + T: Transcript::Field>, + { + crate::verifier::verify_clear(config, input, proof, transcript) + } + + fn verify_zk( + config: &Self::Config, + input: PcsAssistZkInput<'_, DoryScheme>, + proof: &Self::Proof, + transcript: &mut T, + ) -> Result<::HidingCommitment, Self::Error> + where + T: Transcript::Field>, + { + crate::verifier::verify_zk(config, input, proof, transcript) + } +} + +#[derive(Clone, Copy)] +pub enum CheckedInputs<'a> { + Clear(ClearInputs<'a>), + Zk(ZkInputs<'a>), +} + +impl CheckedInputs<'_> { + pub const fn zk(&self) -> bool { + matches!(self, Self::Zk(_)) + } + + pub const fn mode_name(&self) -> &'static str { + if self.zk() { + "zk" + } else { + "clear" + } + } + + pub const fn point(&self) -> &[Fr] { + match self { + Self::Clear(inputs) => inputs.opening.point, + Self::Zk(inputs) => inputs.opening.point, + } + } + + pub const fn pcs_proof(&self) -> &DoryProof { + match self { + Self::Clear(inputs) => inputs.opening.pcs_proof, + Self::Zk(inputs) => inputs.opening.pcs_proof, + } + } + + pub const fn setup(&self) -> &DoryVerifierSetup { + match self { + Self::Clear(inputs) => inputs.opening.setup, + Self::Zk(inputs) => inputs.opening.setup, + } + } +} + +#[derive(Clone, Copy)] +pub struct ClearInputs<'a> { + pub opening: ClearOpeningStatement<'a>, +} + +#[derive(Clone, Copy)] +pub struct ZkInputs<'a> { + pub opening: ZkOpeningStatement<'a>, +} + +#[derive(Clone, Copy)] +pub struct ClearOpeningStatement<'a> { + pub setup: &'a DoryVerifierSetup, + pub pcs_proof: &'a DoryProof, + pub commitment: &'a DoryCommitment, + pub point: &'a [Fr], + pub eval: Fr, +} + +#[derive(Clone, Copy)] +pub struct ZkOpeningStatement<'a> { + pub setup: &'a DoryVerifierSetup, + pub pcs_proof: &'a DoryProof, + pub commitment: &'a DoryCommitment, + pub point: &'a [Fr], +} + +pub fn verify_clear( + config: &DoryAssistConfig, + input: PcsAssistClearInput<'_, DoryScheme>, + proof: &DoryAssistProof, + transcript: &mut T, +) -> Result<(), DoryAssistVerifierError> +where + T: Transcript::Field>, +{ + let _ = config; + let checked = checked_clear_inputs(input); + validate_checked_inputs(&checked)?; + validate_proof_dimensions(&checked, proof)?; + let dory_verifier_scalars = dory_verifier_transcript_scalars(&checked, transcript); + validate_dory_verifier_transcript_scalars(&checked, &dory_verifier_scalars)?; + let input_public_claims = absorb_checked_inputs(&checked, &dory_verifier_scalars, transcript); + verify_checked_input_public_claims(proof, input_public_claims, transcript)?; + let _stage_output = run_stages(&checked, proof, transcript)?; + match checked { + CheckedInputs::Clear(inputs) => { + verify_clear_native_outputs(&inputs.opening, proof, &dory_verifier_scalars) + } + CheckedInputs::Zk(_) => Err(DoryAssistVerifierError::InvalidMode { + expected: "clear", + got: "zk", + }), + } +} + +pub fn verify_zk( + config: &DoryAssistConfig, + input: PcsAssistZkInput<'_, DoryScheme>, + proof: &DoryAssistProof, + transcript: &mut T, +) -> Result<::HidingCommitment, DoryAssistVerifierError> +where + T: Transcript::Field>, +{ + let _ = config; + let checked = checked_zk_inputs(input); + validate_checked_inputs(&checked)?; + validate_proof_dimensions(&checked, proof)?; + let dory_verifier_scalars = dory_verifier_transcript_scalars(&checked, transcript); + validate_dory_verifier_transcript_scalars(&checked, &dory_verifier_scalars)?; + let input_public_claims = absorb_checked_inputs(&checked, &dory_verifier_scalars, transcript); + verify_checked_input_public_claims(proof, input_public_claims, transcript)?; + let _stage_output = run_stages(&checked, proof, transcript)?; + match checked { + CheckedInputs::Zk(inputs) => { + verify_zk_native_outputs(&inputs.opening, proof, &dory_verifier_scalars) + } + CheckedInputs::Clear(_) => Err(DoryAssistVerifierError::InvalidMode { + expected: "zk", + got: "clear", + }), + } +} + +pub fn checked_clear_inputs(input: PcsAssistClearInput<'_, DoryScheme>) -> CheckedInputs<'_> { + CheckedInputs::Clear(ClearInputs { + opening: ClearOpeningStatement::from(input), + }) +} + +pub fn checked_zk_inputs(input: PcsAssistZkInput<'_, DoryScheme>) -> CheckedInputs<'_> { + CheckedInputs::Zk(ZkInputs { + opening: ZkOpeningStatement::from(input), + }) +} + +pub fn validate_checked_inputs(checked: &CheckedInputs<'_>) -> Result<(), DoryAssistVerifierError> { + let point_len = checked.point().len(); + + if point_len > MAX_DORY_ASSIST_OPENING_POINT_LEN { + return Err(DoryAssistVerifierError::CheckedInputMismatch { + reason: format!( + "opening point length {point_len} exceeds maximum {MAX_DORY_ASSIST_OPENING_POINT_LEN}" + ), + }); + } + + let pcs_proof = checked.pcs_proof(); + if point_len != pcs_proof.point_len() { + return Err(DoryAssistVerifierError::CheckedInputMismatch { + reason: format!( + "opening point length {point_len} does not match Dory proof point length {}", + pcs_proof.point_len() + ), + }); + } + if !pcs_proof.has_canonical_reduce_round_shape() { + return Err(DoryAssistVerifierError::CheckedInputMismatch { + reason: format!( + "Dory proof reduce message counts must both equal sigma={}: first={}, second={}", + pcs_proof.reduce_round_count(), + pcs_proof.first_reduce_message_count(), + pcs_proof.second_reduce_message_count() + ), + }); + } + if !checked + .setup() + .supports_reduce_round_count(pcs_proof.reduce_round_count()) + { + return Err(DoryAssistVerifierError::CheckedInputMismatch { + reason: format!( + "Dory verifier setup supports {} reduce rounds with consistent artifacts, but proof requires {}", + checked.setup().max_reduce_rounds(), + pcs_proof.reduce_round_count() + ), + }); + } + match checked { + CheckedInputs::Clear(_) if !pcs_proof.has_transparent_opening_artifacts() => { + return Err(DoryAssistVerifierError::CheckedInputMismatch { + reason: "clear Dory-assist input requires a transparent Dory opening proof" + .to_string(), + }); + } + CheckedInputs::Zk(_) if !pcs_proof.has_zk_opening_artifacts() => { + return Err(DoryAssistVerifierError::CheckedInputMismatch { + reason: "ZK Dory-assist input requires Dory ZK, sigma, and scalar-product proof artifacts" + .to_string(), + }); + } + _ => {} + } + + Ok(()) +} + +pub fn validate_proof_dimensions( + checked: &CheckedInputs<'_>, + proof: &DoryAssistProof, +) -> Result<(), DoryAssistVerifierError> { + let dory_reduce = proof.dimensions.dory_reduce; + let expected_point_len = checked.point().len(); + if dory_reduce.point_len() != expected_point_len { + return Err(DoryAssistVerifierError::InvalidProofShape { + component: "proof.dimensions.dory_reduce.point_len", + reason: format!( + "Dory-reduce point length {} must match checked opening point length {expected_point_len}", + dory_reduce.point_len() + ), + }); + } + + let expected_reduce_rounds = checked.pcs_proof().reduce_round_count(); + if dory_reduce.reduce_rounds() != expected_reduce_rounds { + return Err(DoryAssistVerifierError::InvalidProofShape { + component: "proof.dimensions.dory_reduce.reduce_rounds", + reason: format!( + "Dory-reduce round count {} must match Dory proof reduce round count {expected_reduce_rounds}", + dory_reduce.reduce_rounds() + ), + }); + } + + let supported = default_dory_assist_dimensions(); + if proof.dimensions.gt != supported.gt { + return Err(DoryAssistVerifierError::InvalidProofShape { + component: "proof.dimensions.gt", + reason: format!( + "only canonical GT dimensions {:?} are currently supported, got {:?}", + supported.gt, proof.dimensions.gt + ), + }); + } + if proof.dimensions.g1 != supported.g1 { + return Err(DoryAssistVerifierError::InvalidProofShape { + component: "proof.dimensions.g1", + reason: format!( + "only canonical G1 dimensions {:?} are currently supported, got {:?}", + supported.g1, proof.dimensions.g1 + ), + }); + } + if proof.dimensions.g2 != supported.g2 { + return Err(DoryAssistVerifierError::InvalidProofShape { + component: "proof.dimensions.g2", + reason: format!( + "only canonical G2 dimensions {:?} are currently supported, got {:?}", + supported.g2, proof.dimensions.g2 + ), + }); + } + if proof.dimensions.miller_loop != supported.miller_loop { + return Err(DoryAssistVerifierError::InvalidProofShape { + component: "proof.dimensions.miller_loop", + reason: format!( + "only canonical Miller-loop dimensions {:?} are currently supported, got {:?}", + supported.miller_loop, proof.dimensions.miller_loop + ), + }); + } + if proof.dimensions.wiring != supported.wiring { + return Err(DoryAssistVerifierError::InvalidProofShape { + component: "proof.dimensions.wiring", + reason: format!( + "only canonical wiring dimensions {:?} are currently supported, got {:?}", + supported.wiring, proof.dimensions.wiring + ), + }); + } + + let expected_packing = composition::prefix_packing_catalog(proof.dimensions) + .minimal_dimensions() + .map_err(|error| DoryAssistVerifierError::InvalidProofShape { + component: "proof.dimensions.packing", + reason: error.to_string(), + })?; + if proof.dimensions.packing != expected_packing { + return Err(DoryAssistVerifierError::InvalidProofShape { + component: "proof.dimensions.packing", + reason: format!( + "packing dimensions {:?} must match catalog-derived minimal dimensions {:?}", + proof.dimensions.packing, expected_packing + ), + }); + } + + Ok(()) +} + +pub(crate) fn absorb_checked_inputs( + checked: &CheckedInputs<'_>, + dory_verifier_scalars: &DoryVerifierTranscriptScalars, + transcript: &mut T, +) -> DoryAssistInputPublicClaims +where + T: Transcript::Field>, +{ + let dory_verifier_scalar_claims = dory_verifier_transcript_scalar_claims(dory_verifier_scalars); + transcript.append(&Label(b"DoryAssist")); + transcript.append(&Label(b"checked_inputs")); + transcript.append(&Label(checked.mode_name().as_bytes())); + let mut input_public_claims = match checked { + CheckedInputs::Clear(inputs) => { + let mut input_public_claims = absorb_common_opening_inputs( + inputs.opening.setup, + inputs.opening.pcs_proof, + inputs.opening.commitment, + inputs.opening.point, + transcript, + ); + transcript.append(&Label(b"dory_assist_eval")); + transcript.append(&inputs.opening.eval); + input_public_claims + .jolt_evaluation_claims + .push(inject_fr_to_fq(inputs.opening.eval)); + input_public_claims + .dory_reduce_initial_e2 + .extend(g2_artifact_coordinates( + inputs + .opening + .setup + .artifacts() + .g2_0 + .scalar_mul(&inputs.opening.eval), + )); + input_public_claims + } + CheckedInputs::Zk(inputs) => { + let mut input_public_claims = absorb_common_opening_inputs( + inputs.opening.setup, + inputs.opening.pcs_proof, + inputs.opening.commitment, + inputs.opening.point, + transcript, + ); + input_public_claims + .dory_reduce_initial_e2 + .extend(g2_artifact_coordinates( + inputs + .opening + .pcs_proof + .zk_artifacts() + .e2 + .unwrap_or_default(), + )); + input_public_claims + } + }; + input_public_claims + .transcript_scalars + .extend(checked.point().iter().copied().map(inject_fr_to_fq)); + input_public_claims + .transcript_scalars + .extend(dory_verifier_scalar_claims); + input_public_claims +} + +fn dory_verifier_transcript_scalar_claims(scalars: &DoryVerifierTranscriptScalars) -> Vec { + let mut claims = Vec::with_capacity( + 8 * scalars.reduce_rounds.len() + 4 + usize::from(scalars.scalar_product_sigma_c.is_some()), + ); + for round in &scalars.reduce_rounds { + claims.push(inject_fr_to_fq(round.beta)); + claims.push(inject_fr_to_fq(round.beta_inverse)); + claims.push(inject_fr_to_fq(round.alpha)); + claims.push(inject_fr_to_fq(round.alpha_inverse)); + claims.push(inject_fr_to_fq(round.alpha_beta)); + claims.push(inject_fr_to_fq(round.alpha_inverse_beta_inverse)); + claims.push(inject_fr_to_fq(round.s1_fold_factor)); + claims.push(inject_fr_to_fq(round.s2_fold_factor)); + } + claims.push(inject_fr_to_fq(scalars.gamma)); + claims.push(inject_fr_to_fq(scalars.gamma_inverse)); + if let Some(sigma_c) = scalars.scalar_product_sigma_c { + claims.push(inject_fr_to_fq(sigma_c)); + } + claims.push(inject_fr_to_fq(scalars.d)); + claims.push(inject_fr_to_fq(scalars.d_inverse)); + claims.push(inject_fr_to_fq(scalars.d_squared)); + claims +} + +fn dory_verifier_transcript_scalars( + checked: &CheckedInputs<'_>, + transcript: &T, +) -> DoryVerifierTranscriptScalars +where + T: Transcript::Field>, +{ + checked + .pcs_proof() + .verifier_transcript_scalars(transcript, checked.point()) +} + +fn validate_dory_verifier_transcript_scalars( + checked: &CheckedInputs<'_>, + scalars: &DoryVerifierTranscriptScalars, +) -> Result<(), DoryAssistVerifierError> { + if scalars.has_valid_replay_relations_for_point(checked.point()) { + Ok(()) + } else { + Err(DoryAssistVerifierError::TranscriptMismatch { + reason: "Dory verifier transcript produced a non-invertible challenge or inconsistent derived scalar" + .to_string(), + }) + } +} + +fn absorb_common_opening_inputs( + setup: &DoryVerifierSetup, + pcs_proof: &DoryProof, + commitment: &DoryCommitment, + point: &[Fr], + transcript: &mut T, +) -> DoryAssistInputPublicClaims +where + T: Transcript::Field>, +{ + let mut input_public_claims = DoryAssistInputPublicClaims::default(); + + transcript.append(&Label(b"dory_assist_setup")); + transcript.append(setup); + input_public_claims.verifier_setup_digest = + forked_fq_challenge(transcript, VERIFIER_SETUP_DIGEST_LABEL); + append_dory_verifier_setup_artifacts(&mut input_public_claims.verifier_setup_artifacts, setup); + + transcript.append(&Label(b"dory_assist_pcs_proof")); + transcript.append(pcs_proof); + input_public_claims + .dory_proof_artifacts + .push(forked_fq_challenge(transcript, DORY_PROOF_DIGEST_LABEL)); + append_dory_proof_artifacts(&mut input_public_claims.dory_proof_artifacts, pcs_proof); + + transcript.append(&Label(b"dory_assist_commitment")); + transcript.append(commitment); + input_public_claims + .jolt_commitments + .push(forked_fq_challenge( + transcript, + JOLT_COMMITMENT_DIGEST_LABEL, + )); + input_public_claims + .jolt_commitments + .extend(gt_artifact_coefficients(&commitment.0)); + + transcript.append(&LabelWithCount(b"dory_assist_point", point.len() as u64)); + for point_coordinate in point { + transcript.append(point_coordinate); + } + + input_public_claims +} + +fn append_dory_proof_artifacts(artifacts: &mut Vec, pcs_proof: &DoryProof) { + let vmv = pcs_proof.vmv_artifacts(); + artifacts.extend(gt_artifact_coefficients(&vmv.c)); + artifacts.extend(gt_artifact_coefficients(&vmv.d2)); + artifacts.extend(g1_artifact_coordinates(vmv.e1)); + + let zk = pcs_proof.zk_artifacts(); + artifacts.extend(match zk.e2 { + Some(e2) => g2_artifact_coordinates(e2), + None => identity_g2_artifact_coordinates(), + }); + artifacts.extend(match zk.y_com { + Some(y_com) => g1_artifact_coordinates(y_com), + None => identity_g1_artifact_coordinates(), + }); + if let Some(scalar_product) = pcs_proof.scalar_product_artifacts() { + artifacts.extend(gt_artifact_coefficients(&scalar_product.p1)); + artifacts.extend(gt_artifact_coefficients(&scalar_product.p2)); + artifacts.extend(gt_artifact_coefficients(&scalar_product.q)); + artifacts.extend(gt_artifact_coefficients(&scalar_product.r)); + artifacts.extend(g1_artifact_coordinates(scalar_product.e1)); + artifacts.extend(g2_artifact_coordinates(scalar_product.e2)); + artifacts.push(inject_fr_to_fq(scalar_product.r1)); + artifacts.push(inject_fr_to_fq(scalar_product.r2)); + artifacts.push(inject_fr_to_fq(scalar_product.r3)); + } else { + let identity_gt = Bn254GT::default(); + artifacts.extend(gt_artifact_coefficients(&identity_gt)); + artifacts.extend(gt_artifact_coefficients(&identity_gt)); + artifacts.extend(gt_artifact_coefficients(&identity_gt)); + artifacts.extend(gt_artifact_coefficients(&identity_gt)); + artifacts.extend(identity_g1_artifact_coordinates()); + artifacts.extend(identity_g2_artifact_coordinates()); + artifacts.extend([Fq::default(), Fq::default(), Fq::default()]); + } + + let layout = DoryProofArtifactLayout::for_proof(pcs_proof); + artifacts.reserve(layout.expected_len().saturating_sub(artifacts.len())); + for round in pcs_proof.reduce_round_artifacts() { + artifacts.extend(gt_artifact_coefficients(&round.first.d1_left)); + artifacts.extend(gt_artifact_coefficients(&round.first.d1_right)); + artifacts.extend(gt_artifact_coefficients(&round.first.d2_left)); + artifacts.extend(gt_artifact_coefficients(&round.first.d2_right)); + artifacts.extend(g1_artifact_coordinates(round.first.e1_beta)); + artifacts.extend(g2_artifact_coordinates(round.first.e2_beta)); + + artifacts.extend(gt_artifact_coefficients(&round.second.c_plus)); + artifacts.extend(gt_artifact_coefficients(&round.second.c_minus)); + artifacts.extend(g1_artifact_coordinates(round.second.e1_plus)); + artifacts.extend(g1_artifact_coordinates(round.second.e1_minus)); + artifacts.extend(g2_artifact_coordinates(round.second.e2_plus)); + artifacts.extend(g2_artifact_coordinates(round.second.e2_minus)); + } + let final_artifacts = pcs_proof.final_artifacts(); + artifacts.extend(g1_artifact_coordinates(final_artifacts.e1)); + artifacts.extend(g2_artifact_coordinates(final_artifacts.e2)); +} + +fn append_dory_verifier_setup_artifacts(artifacts: &mut Vec, setup: &DoryVerifierSetup) { + let setup_artifacts = setup.artifacts(); + for value in &setup_artifacts.chi { + artifacts.extend(gt_artifact_coefficients(value)); + } + for value in &setup_artifacts.delta_1l { + artifacts.extend(gt_artifact_coefficients(value)); + } + for value in &setup_artifacts.delta_1r { + artifacts.extend(gt_artifact_coefficients(value)); + } + for value in &setup_artifacts.delta_2l { + artifacts.extend(gt_artifact_coefficients(value)); + } + for value in &setup_artifacts.delta_2r { + artifacts.extend(gt_artifact_coefficients(value)); + } + artifacts.extend(g1_artifact_coordinates(setup_artifacts.g1_0)); + artifacts.extend(g2_artifact_coordinates(setup_artifacts.g2_0)); + artifacts.extend(g1_artifact_coordinates(setup_artifacts.h1)); + artifacts.extend(g2_artifact_coordinates(setup_artifacts.h2)); + artifacts.extend(gt_artifact_coefficients(&setup_artifacts.ht)); +} + +fn gt_artifact_coefficients(value: &Bn254GT) -> [Fq; GT_ARTIFACT_COEFFS] { + let mut coefficients = [Fq::default(); GT_ARTIFACT_COEFFS]; + coefficients[..Bn254GT::FQ12_COEFFICIENTS].copy_from_slice(&value.fq12_coefficients()); + coefficients +} + +fn g1_artifact_coordinates(value: Bn254G1) -> [Fq; G1_ARTIFACT_COORDS] { + value.affine_coordinates_with_infinity() +} + +fn g2_artifact_coordinates(value: Bn254G2) -> [Fq; G2_ARTIFACT_COORDS] { + value.affine_coordinates_with_infinity() +} + +fn identity_g1_artifact_coordinates() -> [Fq; G1_ARTIFACT_COORDS] { + [Fq::default(), Fq::default(), Fq::from_u64(1)] +} + +fn identity_g2_artifact_coordinates() -> [Fq; G2_ARTIFACT_COORDS] { + [ + Fq::default(), + Fq::default(), + Fq::default(), + Fq::default(), + Fq::from_u64(1), + ] +} + +pub(crate) fn inject_fr_to_fq(value: Fr) -> Fq { + let mut bytes = [0_u8; Fr::NUM_BYTES]; + value.to_bytes_le(&mut bytes); + Fq::from_le_bytes_mod_order(&bytes) +} + +pub(crate) fn squeeze_fq(transcript: &mut T) -> Fq +where + T: Transcript, +{ + inject_fr_to_fq(transcript.challenge_scalar()) +} + +pub(crate) fn squeeze_fq_challenge(transcript: &mut T, label: &'static [u8]) -> Fq +where + T: Transcript, +{ + transcript.append(&Label(label)); + squeeze_fq(transcript) +} + +pub(crate) fn forked_fq_challenge(transcript: &T, label: &'static [u8]) -> Fq +where + T: Transcript, +{ + let mut fork = transcript.clone(); + squeeze_fq_challenge(&mut fork, label) +} + +pub(crate) fn squeeze_checked_input_digest(transcript: &mut T) -> Fq +where + T: Transcript, +{ + squeeze_fq_challenge(transcript, CHECKED_INPUT_DIGEST_LABEL) +} + +pub(crate) fn verify_checked_input_public_claims( + proof: &DoryAssistProof, + mut expected: DoryAssistInputPublicClaims, + transcript: &mut T, +) -> Result<(), DoryAssistVerifierError> +where + T: Transcript, +{ + expected.checked_input_digest = squeeze_checked_input_digest(transcript); + let actual = &proof.claims.stage1.public.input; + if actual != &expected { + return Err(DoryAssistVerifierError::CheckedInputMismatch { + reason: checked_input_public_mismatch_reason(actual, &expected), + }); + } + + Ok(()) +} + +fn checked_input_public_mismatch_reason( + actual: &DoryAssistInputPublicClaims, + expected: &DoryAssistInputPublicClaims, +) -> String { + if actual.checked_input_digest != expected.checked_input_digest { + return format!( + "checked-input digest claim {:?} does not match continued transcript digest {:?}", + actual.checked_input_digest, expected.checked_input_digest + ); + } + if actual.verifier_setup_digest != expected.verifier_setup_digest { + return format!( + "verifier setup digest claim {:?} does not match expected {:?}", + actual.verifier_setup_digest, expected.verifier_setup_digest + ); + } + if actual.verifier_setup_artifacts != expected.verifier_setup_artifacts { + return format!( + "verifier setup artifact claims {:?} do not match expected {:?}", + actual.verifier_setup_artifacts, expected.verifier_setup_artifacts + ); + } + if actual.dory_proof_artifacts != expected.dory_proof_artifacts { + return format!( + "Dory proof artifact claims {:?} do not match expected {:?}", + actual.dory_proof_artifacts, expected.dory_proof_artifacts + ); + } + if actual.jolt_commitments != expected.jolt_commitments { + return format!( + "Jolt commitment claims {:?} do not match expected {:?}", + actual.jolt_commitments, expected.jolt_commitments + ); + } + if actual.jolt_evaluation_claims != expected.jolt_evaluation_claims { + return format!( + "Jolt evaluation claims {:?} do not match expected {:?}", + actual.jolt_evaluation_claims, expected.jolt_evaluation_claims + ); + } + if actual.dory_reduce_initial_e2 != expected.dory_reduce_initial_e2 { + return format!( + "Dory-reduce initial E2 claims {:?} do not match expected {:?}", + actual.dory_reduce_initial_e2, expected.dory_reduce_initial_e2 + ); + } + if actual.transcript_scalars != expected.transcript_scalars { + return format!( + "transcript scalar claims {:?} do not match expected {:?}", + actual.transcript_scalars, expected.transcript_scalars + ); + } + + "checked-input public claims do not match expected values".to_string() +} + +pub(crate) fn run_stages( + checked: &CheckedInputs<'_>, + proof: &DoryAssistProof, + transcript: &mut T, +) -> Result +where + T: Transcript::Field>, +{ + validate_proof_dimensions(checked, proof)?; + + let stage1 = stages::stage1::verify( + stages::stage1::Stage1Inputs { + checked, + dimensions: proof.dimensions, + proof: &proof.stages.stage1, + claims: &proof.claims, + }, + transcript, + )?; + let stage2 = stages::stage2::verify( + stages::stage2::Stage2Inputs { + checked, + dimensions: proof.dimensions, + proof: &proof.stages.stage2, + claims: &proof.claims, + stage1: &stage1, + }, + transcript, + )?; + stages::stage3::verify( + stages::stage3::Stage3Inputs { + checked, + proof: &proof.stages.stage3, + opening_proof: &proof.opening_proof, + claims: &proof.claims, + dense_commitment: &proof.dense_commitment, + public_outputs: &proof.public_outputs, + stage1: &stage1, + stage2: &stage2, + }, + transcript, + ) +} + +pub(crate) fn verify_native_outputs( + proof: &DoryAssistProof, +) -> Result<(), DoryAssistVerifierError> { + let expected = proof.public_outputs.pre_final_exponentiation_coefficients(); + let actual = proof.claims.stage1.public.miller_loop.output_gt; + + if let Some((component, (actual, expected))) = actual + .iter() + .zip(expected.iter()) + .enumerate() + .find(|(_, (actual, expected))| actual != expected) + { + return Err(DoryAssistVerifierError::PublicOutputMismatch { + reason: format!( + "MillerLoopOutputGt({component}) claim {actual:?} does not match pre-final-exponentiation coefficient {expected:?}" + ), + }); + } + + Ok(()) +} + +pub(crate) fn verify_clear_native_outputs( + input: &ClearOpeningStatement<'_>, + proof: &DoryAssistProof, + scalars: &DoryVerifierTranscriptScalars, +) -> Result<(), DoryAssistVerifierError> { + verify_native_outputs(proof)?; + + let final_check = transparent_final_pairing_check( + input, + scalars, + &proof.claims.stage1.public.native_final.inputs, + )?; + let final_value = proof + .public_outputs + .pre_final_exponentiation + .final_exponentiation() + .ok_or_else(|| DoryAssistVerifierError::PublicOutputMismatch { + reason: "pre-final-exponentiation output did not admit BN254 final exponentiation" + .to_string(), + })?; + + if final_value != final_check.rhs { + return Err(DoryAssistVerifierError::PublicOutputMismatch { + reason: "final exponentiation of MillerLoopOutputGt did not match Dory final RHS" + .to_string(), + }); + } + + Ok(()) +} + +pub(crate) fn verify_zk_native_outputs( + input: &ZkOpeningStatement<'_>, + proof: &DoryAssistProof, + scalars: &DoryVerifierTranscriptScalars, +) -> Result { + verify_native_outputs(proof)?; + + let final_check = zk_final_pairing_check( + input, + scalars, + &proof.claims.stage1.public.native_final.inputs, + )?; + let final_value = proof + .public_outputs + .pre_final_exponentiation + .final_exponentiation() + .ok_or_else(|| DoryAssistVerifierError::PublicOutputMismatch { + reason: "ZK pre-final-exponentiation output did not admit BN254 final exponentiation" + .to_string(), + })?; + + if final_value != final_check.rhs { + return Err(DoryAssistVerifierError::PublicOutputMismatch { + reason: "final exponentiation of ZK MillerLoopOutputGt did not match Dory scalar-product RHS" + .to_string(), + }); + } + + input.pcs_proof.zk_artifacts().y_com.ok_or_else(|| { + DoryAssistVerifierError::CheckedInputMismatch { + reason: "ZK Dory-assist input is missing y_com hiding commitment".to_string(), + } + }) +} + +impl<'a> From> for ClearOpeningStatement<'a> { + fn from(input: PcsAssistClearInput<'a, DoryScheme>) -> Self { + Self { + setup: input.setup, + pcs_proof: input.pcs_proof, + commitment: input.commitment, + point: input.point, + eval: input.eval, + } + } +} + +impl<'a> From> for ZkOpeningStatement<'a> { + fn from(input: PcsAssistZkInput<'a, DoryScheme>) -> Self { + Self { + setup: input.setup, + pcs_proof: input.pcs_proof, + commitment: input.commitment, + point: input.point, + } + } +} + +#[cfg(test)] +#[expect( + clippy::expect_used, + clippy::panic, + reason = "tests may panic on invalid local setup" +)] +mod tests { + use super::*; + use crate::{ + artifacts::{ + DoryProofArtifactLayout, DORY_VMV_C_START, DORY_VMV_E1_START, G1_ARTIFACT_COORDS, + GT_ARTIFACT_COEFFS, + }, + derive_hyrax_prover_setup, + native_final::{ + transparent_native_final_input_claims, transparent_replayed_final_pairing_check, + zk_native_final_input_claims, zk_replayed_final_pairing_check, + }, + proof::{ + DoryAssistOpeningClaim, DoryAssistStage1PublicClaims, NATIVE_FINAL_D1_START, + NATIVE_FINAL_GT_C_START, NATIVE_FINAL_INPUT_LEN, + }, + stages::stage3::Stage3Output, + DoryAssistHyrax, DoryAssistStage, + }; + use jolt_claims::protocols::dory_assist::{ + formulas::{ + composition, dory_reduce, + protocol::{protocol_claims, CANONICAL_RELATION_ORDER}, + setup_artifacts, transcript_scalars, + }, + DoryAssistChallengeId, DoryAssistCopyConstraint, DoryAssistDimensions, DoryAssistOpeningId, + DoryAssistPublicId, DoryAssistRelationId, DoryAssistSumcheckSpec, DoryAssistValueRef, + DoryAssistVirtualPolynomial, DoryReduceDimensions, DoryReducePolynomial, G1Dimensions, + G2Dimensions, GtDimensions, MillerLoopDimensions, PrefixPackingDimensions, + WiringDimensions, + }; + use jolt_crypto::JoltGroup; + use jolt_field::{Fq, FromPrimitiveInt, Invertible}; + use jolt_hyrax::HyraxDimensions; + use jolt_openings::{CommitmentScheme, ZkOpeningScheme}; + use jolt_poly::{CompressedPoly, EqPolynomial, Polynomial}; + use jolt_sumcheck::SUMCHECK_ROUND_TRANSCRIPT_LABEL; + use jolt_transcript::{Blake2bTranscript, U64Word}; + + #[test] + fn dory_assist_implements_pcs_assist_for_dory() { + fn assert_impl>() {} + assert_impl::(); + } + + #[test] + fn selected_config_is_deterministic() { + let left = DoryAssist::selected_config(); + let right = DoryAssist::selected_config(); + + assert_eq!(left, right); + } + + fn absorb_checked_inputs_for_test( + checked: &CheckedInputs<'_>, + transcript: &mut T, + ) -> DoryAssistInputPublicClaims + where + T: Transcript::Field>, + { + let scalars = dory_verifier_transcript_scalars(checked, transcript); + assert!(scalars.has_valid_replay_relations_for_point(checked.point())); + absorb_checked_inputs(checked, &scalars, transcript) + } + + #[test] + fn checked_clear_inputs_preserve_opening_statement() -> Result<(), &'static str> { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + + let CheckedInputs::Clear(inputs) = checked else { + return Err("expected clear checked inputs"); + }; + assert_clear_opening_matches(&inputs.opening, &fixture); + Ok(()) + } + + #[test] + fn checked_zk_inputs_preserve_opening_statement() -> Result<(), &'static str> { + let fixture = dory_opening_fixture(); + let checked = checked_zk_inputs(fixture.zk_input()); + + let CheckedInputs::Zk(inputs) = checked else { + return Err("expected zk checked inputs"); + }; + assert_zk_opening_matches(&inputs.opening, &fixture); + Ok(()) + } + + #[test] + fn checked_inputs_expose_jolt_like_mode_flag() { + let fixture = dory_opening_fixture(); + let clear = checked_clear_inputs(fixture.clear_input()); + let zk = checked_zk_inputs(fixture.zk_input()); + + assert!(!clear.zk()); + assert_eq!(clear.mode_name(), "clear"); + assert!(zk.zk()); + assert_eq!(zk.mode_name(), "zk"); + } + + #[test] + fn fr_to_fq_injection_preserves_small_scalars() { + let scalar = Fr::from_u64(42); + + assert_eq!(inject_fr_to_fq(scalar), Fq::from_u64(42)); + assert_eq!(inject_fr_to_fq(scalar), inject_fr_to_fq(scalar)); + } + + #[test] + fn checked_input_validation_rejects_excessively_long_points() { + let fixture = dory_opening_fixture(); + let point = vec![Fr::from_u64(1); MAX_DORY_ASSIST_OPENING_POINT_LEN + 1]; + let checked = checked_clear_inputs(PcsAssistClearInput { + setup: &fixture.verifier_setup, + pcs_proof: &fixture.proof, + commitment: &fixture.commitment, + point: &point, + eval: fixture.eval, + }); + + let result = validate_checked_inputs(&checked); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::CheckedInputMismatch { .. }) + )); + } + + #[test] + fn checked_input_validation_rejects_point_len_mismatch() { + let fixture = dory_opening_fixture(); + let point = vec![Fr::from_u64(1)]; + let checked = checked_clear_inputs(PcsAssistClearInput { + setup: &fixture.verifier_setup, + pcs_proof: &fixture.proof, + commitment: &fixture.commitment, + point: &point, + eval: fixture.eval, + }); + + let result = validate_checked_inputs(&checked); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::CheckedInputMismatch { .. }) + )); + } + + #[test] + fn checked_input_validation_rejects_reduce_round_shape_mismatch() { + let mut fixture = dory_opening_fixture(); + let _ = fixture.proof.0.first_messages.pop(); + let checked = checked_clear_inputs(fixture.clear_input()); + + let result = validate_checked_inputs(&checked); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::CheckedInputMismatch { .. }) + )); + } + + #[test] + fn checked_input_validation_rejects_setup_with_insufficient_reduce_round_capacity() { + let mut fixture = dory_opening_fixture_with_num_vars(4); + assert_eq!(fixture.proof.reduce_round_count(), 2); + let (_, smaller_setup) = DoryScheme::setup(2); + assert_eq!(smaller_setup.max_reduce_rounds(), 1); + fixture.verifier_setup = smaller_setup; + let checked = checked_clear_inputs(fixture.clear_input()); + + let result = validate_checked_inputs(&checked); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::CheckedInputMismatch { .. }) + )); + } + + #[test] + fn checked_input_validation_rejects_inconsistent_setup_artifact_lengths() { + let mut fixture = dory_opening_fixture(); + let _ = fixture.verifier_setup.0.delta_1l.pop(); + let checked = checked_clear_inputs(fixture.clear_input()); + + let result = validate_checked_inputs(&checked); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::CheckedInputMismatch { .. }) + )); + } + + #[test] + fn checked_input_validation_rejects_clear_with_zk_dory_proof() { + let fixture = dory_zk_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + + let result = validate_checked_inputs(&checked); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::CheckedInputMismatch { .. }) + )); + } + + #[test] + fn checked_input_validation_rejects_zk_without_zk_dory_artifacts() { + let fixture = dory_opening_fixture(); + let checked = checked_zk_inputs(fixture.zk_input()); + + let result = validate_checked_inputs(&checked); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::CheckedInputMismatch { .. }) + )); + } + + #[test] + fn checked_input_validation_rejects_zk_missing_scalar_product_artifacts() { + let mut fixture = dory_zk_opening_fixture(); + fixture.proof.0.scalar_product_proof = None; + let checked = checked_zk_inputs(fixture.zk_input()); + + let result = validate_checked_inputs(&checked); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::CheckedInputMismatch { .. }) + )); + } + + #[test] + fn proof_dimension_validation_rejects_dory_reduce_point_len_mismatch() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof_for_checked(&checked); + proof.dimensions.dory_reduce = + DoryReduceDimensions::new(fixture.point.len() + 1, fixture.proof.reduce_round_count()); + + let result = validate_proof_dimensions(&checked, &proof); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::InvalidProofShape { + component: "proof.dimensions.dory_reduce.point_len", + .. + }) + )); + } + + #[test] + fn proof_dimension_validation_rejects_dory_reduce_round_count_mismatch() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof_for_checked(&checked); + proof.dimensions.dory_reduce = + DoryReduceDimensions::new(fixture.point.len(), fixture.proof.reduce_round_count() + 1); + + let result = validate_proof_dimensions(&checked, &proof); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::InvalidProofShape { + component: "proof.dimensions.dory_reduce.reduce_rounds", + .. + }) + )); + } + + #[test] + fn proof_dimension_validation_accepts_matching_multiround_dory_reduce() { + let fixture = dory_opening_fixture_with_num_vars(4); + assert_eq!(fixture.proof.point_len(), 4); + assert_eq!(fixture.proof.reduce_round_count(), 2); + let checked = checked_clear_inputs(fixture.clear_input()); + let proof = well_shaped_assist_proof_for_checked(&checked); + + assert_eq!( + proof.dimensions.dory_reduce, + DoryReduceDimensions::new( + fixture.proof.point_len(), + fixture.proof.reduce_round_count(), + ) + ); + + let result = validate_proof_dimensions(&checked, &proof); + + assert_eq!(result, Ok(())); + } + + #[test] + fn verify_clear_accepts_well_shaped_multiround_transparent_proof() { + let fixture = dory_opening_fixture_with_num_vars(4); + assert_eq!(fixture.proof.point_len(), 4); + assert_eq!(fixture.proof.reduce_round_count(), 2); + let checked = checked_clear_inputs(fixture.clear_input()); + let proof = well_shaped_assist_proof_for_checked(&checked); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let result = DoryAssist::verify_clear( + &DoryAssist::selected_config(), + fixture.clear_input(), + &proof, + &mut transcript, + ); + + assert_eq!(result, Ok(())); + } + + #[test] + fn run_stages_rejects_multiround_dory_reduce_public_fold_mismatch() { + let fixture = dory_opening_fixture_with_num_vars(4); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof_for_checked(&checked); + let opening = dory_reduce::s1_fold_factor_opening(); + let tampered = get_dory_reduce_opening(&proof, opening) + Fq::from_u64(1); + set_dory_reduce_opening(&mut proof, opening, tampered); + rebalance_dory_reduce_scalar_fold_relation(&mut proof, &checked); + populate_valid_hyrax_opening(&mut proof); + + let result = try_run_stages_after_checked_preamble(&checked, &proof); + + assert!( + matches!( + result, + Err(DoryAssistVerifierError::StageOutputMismatch { + stage: DoryAssistStage::Stage2, + .. + }) + ), + "{result:?}" + ); + } + + #[test] + fn run_stages_rejects_multiround_dory_reduce_boundary_output_mismatch() { + let fixture = dory_opening_fixture_with_num_vars(4); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof_for_checked(&checked); + set_dory_reduce_opening( + &mut proof, + dory_reduce_opening( + DoryAssistRelationId::DoryReduceBoundary, + DoryReducePolynomial::S1Accumulator, + ), + Fq::from_u64(9), + ); + populate_valid_hyrax_opening(&mut proof); + + let result = try_run_stages_after_checked_preamble(&checked, &proof); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::StageOutputMismatch { + stage: DoryAssistStage::Stage1, + .. + }) + )); + } + + #[test] + fn run_stages_rejects_multiround_dory_reduce_state_chain_output_mismatch() { + let fixture = dory_opening_fixture_with_num_vars(4); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof_for_checked(&checked); + set_dory_reduce_opening( + &mut proof, + dory_reduce_opening( + DoryAssistRelationId::DoryReduceStateChain, + DoryReducePolynomial::S1Accumulator, + ), + Fq::from_u64(7), + ); + populate_valid_hyrax_opening(&mut proof); + + let result = try_run_stages_after_checked_preamble(&checked, &proof); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::StageOutputMismatch { + stage: DoryAssistStage::Stage1, + .. + }) + )); + } + + #[test] + fn proof_dimension_validation_rejects_multiround_dory_reduce_point_mismatch() { + let fixture = dory_opening_fixture_with_num_vars(4); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof_for_checked(&checked); + proof.dimensions.dory_reduce = DoryReduceDimensions::new( + fixture.proof.point_len() + 1, + fixture.proof.reduce_round_count(), + ); + + let result = validate_proof_dimensions(&checked, &proof); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::InvalidProofShape { + component: "proof.dimensions.dory_reduce.point_len", + .. + }) + )); + } + + #[test] + fn proof_dimension_validation_rejects_noncanonical_gt_dimensions() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof_for_checked(&checked); + let gt = proof.dimensions.gt; + proof.dimensions.gt = GtDimensions::new( + gt.exp_step_vars() + 1, + gt.exp_instance_vars(), + gt.mul_instance_vars(), + ); + + let result = validate_proof_dimensions(&checked, &proof); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::InvalidProofShape { + component: "proof.dimensions.gt", + .. + }) + )); + } + + #[test] + fn proof_dimension_validation_rejects_noncanonical_g1_dimensions() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof_for_checked(&checked); + let g1 = proof.dimensions.g1; + proof.dimensions.g1 = G1Dimensions::new( + g1.scalar_mul_step_vars() + 1, + g1.scalar_mul_instance_vars(), + g1.add_instance_vars(), + ); + + let result = validate_proof_dimensions(&checked, &proof); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::InvalidProofShape { + component: "proof.dimensions.g1", + .. + }) + )); + } + + #[test] + fn proof_dimension_validation_rejects_noncanonical_g2_dimensions() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof_for_checked(&checked); + let g2 = proof.dimensions.g2; + proof.dimensions.g2 = G2Dimensions::new( + g2.scalar_mul_step_vars() + 1, + g2.scalar_mul_instance_vars(), + g2.add_instance_vars(), + ); + + let result = validate_proof_dimensions(&checked, &proof); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::InvalidProofShape { + component: "proof.dimensions.g2", + .. + }) + )); + } + + #[test] + fn proof_dimension_validation_rejects_noncanonical_miller_loop_dimensions() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof_for_checked(&checked); + let miller_loop = proof.dimensions.miller_loop; + proof.dimensions.miller_loop = MillerLoopDimensions::new( + miller_loop.line_event_vars() + 1, + miller_loop.pair_vars(), + miller_loop.accumulator_op_vars(), + ); + + let result = validate_proof_dimensions(&checked, &proof); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::InvalidProofShape { + component: "proof.dimensions.miller_loop", + .. + }) + )); + } + + #[test] + fn proof_dimension_validation_rejects_noncanonical_wiring_dimensions() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof_for_checked(&checked); + proof.dimensions.wiring = WiringDimensions::new(proof.dimensions.wiring.log_edges() + 1); + + let result = validate_proof_dimensions(&checked, &proof); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::InvalidProofShape { + component: "proof.dimensions.wiring", + .. + }) + )); + } + + #[test] + fn proof_dimension_validation_rejects_nonminimal_packing_dimensions() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof_for_checked(&checked); + let packing = proof.dimensions.packing; + proof.dimensions.packing = PrefixPackingDimensions::new( + packing.packed_vars() + 1, + packing.max_poly_vars(), + packing.num_claims(), + ) + .expect("valid non-minimal packing dimensions"); + + let result = validate_proof_dimensions(&checked, &proof); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::InvalidProofShape { + component: "proof.dimensions.packing", + .. + }) + )); + } + + #[test] + fn checked_input_preamble_is_deterministic_for_clear() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut left = Blake2bTranscript::new(b"dory-assist-test"); + let mut right = Blake2bTranscript::new(b"dory-assist-test"); + + let _ = absorb_checked_inputs_for_test(&checked, &mut left); + let _ = absorb_checked_inputs_for_test(&checked, &mut right); + + assert_eq!(left.state(), right.state()); + } + + #[test] + fn checked_input_preamble_changes_when_clear_eval_changes() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut changed = fixture.clear_input(); + changed.eval += Fr::from_u64(1); + let changed = checked_clear_inputs(changed); + let mut left = Blake2bTranscript::new(b"dory-assist-test"); + let mut right = Blake2bTranscript::new(b"dory-assist-test"); + + let _ = absorb_checked_inputs_for_test(&checked, &mut left); + let _ = absorb_checked_inputs_for_test(&changed, &mut right); + + assert_ne!(left.state(), right.state()); + } + + #[test] + fn checked_input_challenge_changes_when_setup_changes() { + let fixture = dory_opening_fixture(); + let (_, changed_setup) = DoryScheme::setup(3); + let checked = checked_clear_inputs(fixture.clear_input()); + let changed = checked_clear_inputs(PcsAssistClearInput { + setup: &changed_setup, + pcs_proof: &fixture.proof, + commitment: &fixture.commitment, + point: &fixture.point, + eval: fixture.eval, + }); + + assert_ne!( + checked_input_test_challenge(&checked), + checked_input_test_challenge(&changed) + ); + } + + #[test] + fn checked_input_challenge_changes_when_pcs_proof_changes() { + let fixture = dory_opening_fixture(); + let changed_fixture = dory_opening_fixture_with_shift(9); + let checked = checked_clear_inputs(fixture.clear_input()); + let changed = checked_clear_inputs(PcsAssistClearInput { + setup: &fixture.verifier_setup, + pcs_proof: &changed_fixture.proof, + commitment: &fixture.commitment, + point: &fixture.point, + eval: fixture.eval, + }); + + assert_ne!( + checked_input_test_challenge(&checked), + checked_input_test_challenge(&changed) + ); + } + + #[test] + fn checked_input_digest_is_deterministic_for_clear() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + + assert_eq!( + checked_input_public_claims_for_test(&checked).checked_input_digest, + checked_input_public_claims_for_test(&checked).checked_input_digest + ); + } + + #[test] + fn checked_input_digest_rejects_mismatched_claim() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof_for_checked(&checked); + proof.claims.stage1.public.input.checked_input_digest += Fq::from_u64(1); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + let input_public_claims = absorb_checked_inputs_for_test(&checked, &mut transcript); + + let result = + verify_checked_input_public_claims(&proof, input_public_claims, &mut transcript); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::CheckedInputMismatch { .. }) + )); + } + + #[test] + fn checked_input_public_claims_reject_mismatched_dory_reduce_initial_e2() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof_for_checked(&checked); + proof.claims.stage1.public.input.dory_reduce_initial_e2[0] += Fq::from_u64(1); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + let input_public_claims = absorb_checked_inputs_for_test(&checked, &mut transcript); + + let result = + verify_checked_input_public_claims(&proof, input_public_claims, &mut transcript); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::CheckedInputMismatch { .. }) + )); + } + + #[test] + fn checked_input_public_claims_resolve_public_ids() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let input_claims = checked_input_public_claims_for_test(&checked); + + assert_eq!( + input_claims.claim(&DoryAssistPublicId::VerifierSetupDigest), + Some(input_claims.verifier_setup_digest) + ); + assert_eq!( + input_claims.claim(&DoryAssistPublicId::VerifierSetupArtifact(0)), + input_claims.verifier_setup_artifacts.first().copied() + ); + assert_eq!( + input_claims.claim(&DoryAssistPublicId::DoryProofArtifact(0)), + input_claims.dory_proof_artifacts.first().copied() + ); + assert_eq!( + input_claims.claim(&DoryAssistPublicId::JoltCommitment(0)), + input_claims.jolt_commitments.first().copied() + ); + assert_eq!( + input_claims.claim(&DoryAssistPublicId::JoltCommitment(1)), + input_claims.jolt_commitments.get(1).copied() + ); + assert_eq!( + input_claims.claim(&DoryAssistPublicId::JoltEvaluationClaim(0)), + Some(inject_fr_to_fq(fixture.eval)) + ); + assert_eq!( + input_claims.claim(&DoryAssistPublicId::DoryReduceInitialE2(0)), + input_claims.dory_reduce_initial_e2.first().copied() + ); + assert_eq!( + input_claims.claim(&DoryAssistPublicId::TranscriptScalar(1)), + Some(inject_fr_to_fq(fixture.point[1])) + ); + let dory_transcript = Blake2bTranscript::::new(b"dory-assist-test"); + let dory_scalars = fixture + .proof + .verifier_transcript_scalars(&dory_transcript, &fixture.point); + assert_eq!( + input_claims.claim(&DoryAssistPublicId::TranscriptScalar( + transcript_scalars::dory_reduce_beta(fixture.point.len(), 0), + )), + Some(inject_fr_to_fq(dory_scalars.reduce_rounds[0].beta)) + ); + assert_eq!( + input_claims.claim(&DoryAssistPublicId::TranscriptScalar( + transcript_scalars::dory_reduce_alpha(fixture.point.len(), 0), + )), + Some(inject_fr_to_fq(dory_scalars.reduce_rounds[0].alpha)) + ); + assert_eq!( + input_claims.claim(&DoryAssistPublicId::TranscriptScalar( + transcript_scalars::dory_reduce_alpha_beta(fixture.point.len(), 0), + )), + Some(inject_fr_to_fq(dory_scalars.reduce_rounds[0].alpha_beta)) + ); + assert_eq!( + input_claims.claim(&DoryAssistPublicId::TranscriptScalar( + transcript_scalars::dory_reduce_s1_fold_factor(fixture.point.len(), 0), + )), + Some(inject_fr_to_fq( + dory_scalars.reduce_rounds[0].s1_fold_factor + )) + ); + assert_eq!( + input_claims.claim(&DoryAssistPublicId::TranscriptScalar( + transcript_scalars::dory_gamma(fixture.point.len(), 1), + )), + Some(inject_fr_to_fq(dory_scalars.gamma)) + ); + assert_eq!( + input_claims.claim(&DoryAssistPublicId::TranscriptScalar( + transcript_scalars::dory_gamma_inverse(fixture.point.len(), 1), + )), + Some(inject_fr_to_fq(dory_scalars.gamma_inverse)) + ); + assert_eq!( + input_claims.claim(&DoryAssistPublicId::TranscriptScalar( + transcript_scalars::dory_final_d(fixture.point.len(), 1, false), + )), + Some(inject_fr_to_fq(dory_scalars.d)) + ); + assert_eq!( + input_claims.claim(&DoryAssistPublicId::TranscriptScalar( + transcript_scalars::dory_final_d_squared(fixture.point.len(), 1, false), + )), + Some(inject_fr_to_fq(dory_scalars.d_squared)) + ); + assert_eq!( + input_claims.transcript_scalars.len(), + transcript_scalars::transcript_scalar_count(fixture.point.len(), 1, false) + ); + + let vmv = fixture.proof.vmv_artifacts(); + let layout = DoryProofArtifactLayout::for_proof(&fixture.proof); + let setup_artifacts = fixture.verifier_setup.artifacts(); + assert_eq!( + input_claims.verifier_setup_artifacts.len(), + setup_artifacts::dory_setup_artifact_count(fixture.verifier_setup.max_reduce_rounds()) + ); + assert_eq!( + &input_claims.verifier_setup_artifacts[setup_artifacts::dory_setup_chi_start(0) + ..setup_artifacts::dory_setup_chi_start(0) + GT_ARTIFACT_COEFFS], + gt_artifact_coefficients(&setup_artifacts.chi[0]).as_slice() + ); + assert_eq!( + &input_claims.verifier_setup_artifacts[setup_artifacts::dory_setup_delta_1l_start( + fixture.verifier_setup.max_reduce_rounds(), + 0, + ) + ..setup_artifacts::dory_setup_delta_1l_start( + fixture.verifier_setup.max_reduce_rounds(), + 0, + ) + GT_ARTIFACT_COEFFS], + gt_artifact_coefficients(&setup_artifacts.delta_1l[0]).as_slice() + ); + assert_eq!( + &input_claims.verifier_setup_artifacts[setup_artifacts::dory_setup_g1_0_start( + fixture.verifier_setup.max_reduce_rounds() + ) + ..setup_artifacts::dory_setup_g1_0_start( + fixture.verifier_setup.max_reduce_rounds() + ) + G1_ARTIFACT_COORDS], + g1_artifact_coordinates(setup_artifacts.g1_0).as_slice() + ); + assert_eq!( + input_claims.dory_proof_artifacts.len(), + layout.expected_len() + ); + assert_eq!(input_claims.jolt_commitments.len(), 17); + assert_eq!( + &input_claims.dory_proof_artifacts[layout.vmv_c()], + gt_artifact_coefficients(&vmv.c).as_slice() + ); + assert_eq!( + &input_claims.dory_proof_artifacts[layout.vmv_d2()], + gt_artifact_coefficients(&vmv.d2).as_slice() + ); + assert_eq!( + &input_claims.dory_proof_artifacts[layout.vmv_e1()], + g1_artifact_coordinates(vmv.e1).as_slice() + ); + let round = fixture + .proof + .reduce_round_artifacts() + .into_iter() + .next() + .expect("fixture has one Dory reduce round"); + let round_layout = layout.reduce_round(0); + assert_eq!( + &input_claims.dory_proof_artifacts[round_layout.first_d1_left()], + gt_artifact_coefficients(&round.first.d1_left).as_slice() + ); + assert_eq!( + &input_claims.dory_proof_artifacts[round_layout.first_d1_right()], + gt_artifact_coefficients(&round.first.d1_right).as_slice() + ); + assert_eq!( + &input_claims.dory_proof_artifacts[round_layout.first_d2_left()], + gt_artifact_coefficients(&round.first.d2_left).as_slice() + ); + assert_eq!( + &input_claims.dory_proof_artifacts[round_layout.first_d2_right()], + gt_artifact_coefficients(&round.first.d2_right).as_slice() + ); + assert_eq!( + &input_claims.dory_proof_artifacts[round_layout.first_e1_beta()], + g1_artifact_coordinates(round.first.e1_beta).as_slice() + ); + assert_eq!( + &input_claims.dory_proof_artifacts[round_layout.first_e2_beta()], + g2_artifact_coordinates(round.first.e2_beta).as_slice() + ); + assert_eq!( + &input_claims.dory_proof_artifacts[round_layout.second_c_plus()], + gt_artifact_coefficients(&round.second.c_plus).as_slice() + ); + assert_eq!( + &input_claims.dory_proof_artifacts[round_layout.second_c_minus()], + gt_artifact_coefficients(&round.second.c_minus).as_slice() + ); + assert_eq!( + &input_claims.dory_proof_artifacts[round_layout.second_e1_plus()], + g1_artifact_coordinates(round.second.e1_plus).as_slice() + ); + assert_eq!( + &input_claims.dory_proof_artifacts[round_layout.second_e1_minus()], + g1_artifact_coordinates(round.second.e1_minus).as_slice() + ); + assert_eq!( + &input_claims.dory_proof_artifacts[round_layout.second_e2_plus()], + g2_artifact_coordinates(round.second.e2_plus).as_slice() + ); + assert_eq!( + &input_claims.dory_proof_artifacts[round_layout.second_e2_minus()], + g2_artifact_coordinates(round.second.e2_minus).as_slice() + ); + let final_artifacts = fixture.proof.final_artifacts(); + assert_eq!( + &input_claims.dory_proof_artifacts[layout.final_e1()], + g1_artifact_coordinates(final_artifacts.e1).as_slice() + ); + assert_eq!( + &input_claims.dory_proof_artifacts[layout.final_e2()], + g2_artifact_coordinates(final_artifacts.e2).as_slice() + ); + assert_eq!( + &input_claims.jolt_commitments[1..17], + gt_artifact_coefficients(&fixture.commitment.0).as_slice() + ); + } + + #[test] + fn checked_input_public_claims_resolve_zk_artifacts_and_sigma_c() { + let fixture = dory_zk_opening_fixture(); + let checked = checked_zk_inputs(fixture.zk_input()); + let input_claims = checked_input_public_claims_for_test(&checked); + let layout = DoryProofArtifactLayout::for_proof(&fixture.proof); + let zk_artifacts = fixture.proof.zk_artifacts(); + let scalar_product = fixture + .proof + .scalar_product_artifacts() + .expect("ZK fixture carries scalar-product artifacts"); + let dory_transcript = Blake2bTranscript::::new(b"dory-assist-test"); + let dory_scalars = fixture + .proof + .verifier_transcript_scalars(&dory_transcript, &fixture.point); + let e2 = zk_artifacts.e2.expect("ZK fixture carries E2 artifact"); + let y_com = zk_artifacts + .y_com + .expect("ZK fixture carries y_com artifact"); + let sigma_c = dory_scalars + .scalar_product_sigma_c + .expect("ZK fixture has scalar-product sigma_c"); + + assert!(input_claims.jolt_evaluation_claims.is_empty()); + assert_eq!( + input_claims.dory_reduce_initial_e2, + g2_artifact_coordinates(e2).to_vec() + ); + assert_eq!( + &input_claims.dory_proof_artifacts[layout.zk_e2()], + g2_artifact_coordinates(e2).as_slice() + ); + assert_eq!( + &input_claims.dory_proof_artifacts[layout.zk_y_com()], + g1_artifact_coordinates(y_com).as_slice() + ); + assert_eq!( + &input_claims.dory_proof_artifacts[layout.scalar_product_p1()], + gt_artifact_coefficients(&scalar_product.p1).as_slice() + ); + assert_eq!( + &input_claims.dory_proof_artifacts[layout.scalar_product_p2()], + gt_artifact_coefficients(&scalar_product.p2).as_slice() + ); + assert_eq!( + &input_claims.dory_proof_artifacts[layout.scalar_product_q()], + gt_artifact_coefficients(&scalar_product.q).as_slice() + ); + assert_eq!( + &input_claims.dory_proof_artifacts[layout.scalar_product_r()], + gt_artifact_coefficients(&scalar_product.r).as_slice() + ); + assert_eq!( + &input_claims.dory_proof_artifacts[layout.scalar_product_e1()], + g1_artifact_coordinates(scalar_product.e1).as_slice() + ); + assert_eq!( + &input_claims.dory_proof_artifacts[layout.scalar_product_e2()], + g2_artifact_coordinates(scalar_product.e2).as_slice() + ); + assert_eq!( + input_claims.dory_proof_artifacts[layout.scalar_product_r1()], + inject_fr_to_fq(scalar_product.r1) + ); + assert_eq!( + input_claims.dory_proof_artifacts[layout.scalar_product_r2()], + inject_fr_to_fq(scalar_product.r2) + ); + assert_eq!( + input_claims.dory_proof_artifacts[layout.scalar_product_r3()], + inject_fr_to_fq(scalar_product.r3) + ); + assert_eq!( + input_claims.claim(&DoryAssistPublicId::TranscriptScalar( + transcript_scalars::dory_scalar_product_sigma_c( + fixture.point.len(), + fixture.proof.reduce_round_count(), + ), + )), + Some(inject_fr_to_fq(sigma_c)) + ); + assert_eq!( + input_claims.claim(&DoryAssistPublicId::TranscriptScalar( + transcript_scalars::dory_final_d( + fixture.point.len(), + fixture.proof.reduce_round_count(), + true, + ), + )), + Some(inject_fr_to_fq(dory_scalars.d)) + ); + assert_eq!( + input_claims.transcript_scalars.len(), + transcript_scalars::transcript_scalar_count( + fixture.point.len(), + fixture.proof.reduce_round_count(), + true, + ) + ); + } + + #[test] + fn stage1_public_claims_resolve_dory_reduce_shift_kernel() { + let public_claims = DoryAssistStage1PublicClaims { + dory_reduce_shift_eq_kernel: Fq::from_u64(17), + ..Default::default() + }; + + assert_eq!( + public_claims.claim(&DoryAssistPublicId::DoryReduceShiftEqKernel), + Some(Fq::from_u64(17)) + ); + } + + #[test] + fn stage1_public_claims_resolve_native_final_inputs() { + let mut public_claims = DoryAssistStage1PublicClaims::default(); + public_claims.native_final.bind( + (0..NATIVE_FINAL_INPUT_LEN) + .map(|index| Fq::from_u64(u64::try_from(index + 1).expect("index fits"))) + .collect(), + ); + + assert_eq!( + public_claims.claim(&DoryAssistPublicId::NativeFinalCheckInput( + NATIVE_FINAL_D1_START + )), + Some(Fq::from_u64( + u64::try_from(NATIVE_FINAL_D1_START + 1).expect("index fits") + )) + ); + } + + #[test] + fn checked_input_preamble_changes_when_zk_point_changes() { + let fixture = dory_opening_fixture(); + let checked = checked_zk_inputs(fixture.zk_input()); + let mut point = fixture.point.clone(); + point[0] += Fr::from_u64(1); + let changed = checked_zk_inputs(PcsAssistZkInput { + setup: &fixture.verifier_setup, + pcs_proof: &fixture.proof, + commitment: &fixture.commitment, + point: &point, + }); + let mut left = Blake2bTranscript::new(b"dory-assist-test"); + let mut right = Blake2bTranscript::new(b"dory-assist-test"); + + let _ = absorb_checked_inputs_for_test(&checked, &mut left); + let _ = absorb_checked_inputs_for_test(&changed, &mut right); + + assert_ne!(left.state(), right.state()); + } + + #[test] + fn verify_clear_accepts_well_shaped_transparent_proof() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let proof = well_shaped_assist_proof_for_checked(&checked); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let result = DoryAssist::verify_clear( + &DoryAssist::selected_config(), + fixture.clear_input(), + &proof, + &mut transcript, + ); + + assert_eq!(result, Ok(())); + } + + #[test] + fn verify_zk_accepts_well_shaped_zk_proof() { + let fixture = dory_zk_opening_fixture(); + let checked = checked_zk_inputs(fixture.zk_input()); + let proof = well_shaped_assist_proof_for_checked(&checked); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let result = DoryAssist::verify_zk( + &DoryAssist::selected_config(), + fixture.zk_input(), + &proof, + &mut transcript, + ); + + assert_eq!( + result, + Ok(fixture + .proof + .zk_artifacts() + .y_com + .expect("ZK fixture has y_com")) + ); + } + + #[test] + fn dory_verifier_transcript_scalars_reject_invalid_replay_relations() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let transcript = Blake2bTranscript::new(b"dory-assist-test"); + let mut scalars = dory_verifier_transcript_scalars(&checked, &transcript); + assert!(validate_dory_verifier_transcript_scalars(&checked, &scalars).is_ok()); + + scalars.reduce_rounds[0].alpha_inverse = Fr::from_u64(0); + + assert!(matches!( + validate_dory_verifier_transcript_scalars(&checked, &scalars), + Err(DoryAssistVerifierError::TranscriptMismatch { .. }) + )); + + let mut scalars = dory_verifier_transcript_scalars(&checked, &transcript); + scalars.reduce_rounds[0].s2_fold_factor += Fr::from_u64(1); + + assert!(matches!( + validate_dory_verifier_transcript_scalars(&checked, &scalars), + Err(DoryAssistVerifierError::TranscriptMismatch { .. }) + )); + } + + #[test] + fn native_outputs_accept_bound_pre_final_exponentiation() { + let mut proof = DoryAssistProof::default(); + proof + .claims + .stage1 + .public + .miller_loop + .bind_pre_final_exponentiation(&proof.public_outputs); + + assert_eq!(verify_native_outputs(&proof), Ok(())); + } + + #[test] + fn native_outputs_reject_mismatched_pre_final_exponentiation() { + let proof = DoryAssistProof::default(); + + assert!(matches!( + verify_native_outputs(&proof), + Err(DoryAssistVerifierError::PublicOutputMismatch { .. }) + )); + } + + #[test] + fn clear_native_outputs_reject_final_exponentiation_mismatch() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof_for_checked(&checked); + proof.public_outputs.pre_final_exponentiation = Default::default(); + proof + .claims + .stage1 + .public + .miller_loop + .bind_pre_final_exponentiation(&proof.public_outputs); + let CheckedInputs::Clear(inputs) = checked else { + panic!("fixture is clear") + }; + let transcript = Blake2bTranscript::new(b"dory-assist-test"); + let scalars = dory_verifier_transcript_scalars(&checked, &transcript); + + assert!(matches!( + verify_clear_native_outputs(&inputs.opening, &proof, &scalars), + Err(DoryAssistVerifierError::PublicOutputMismatch { .. }) + )); + } + + #[test] + fn clear_native_outputs_reject_mismatched_native_final_input_claim() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof_for_checked(&checked); + tamper_native_final_c_acc(&mut proof); + let CheckedInputs::Clear(inputs) = checked else { + panic!("fixture is clear") + }; + let transcript = Blake2bTranscript::new(b"dory-assist-test"); + let scalars = dory_verifier_transcript_scalars(&checked, &transcript); + + assert!(matches!( + verify_clear_native_outputs(&inputs.opening, &proof, &scalars), + Err(DoryAssistVerifierError::PublicOutputMismatch { .. }) + )); + } + + #[test] + fn zk_native_outputs_reject_mismatched_native_final_input_claim() { + let fixture = dory_zk_opening_fixture(); + let checked = checked_zk_inputs(fixture.zk_input()); + let mut proof = well_shaped_assist_proof_for_checked(&checked); + tamper_native_final_c_acc(&mut proof); + let CheckedInputs::Zk(inputs) = checked else { + panic!("fixture is ZK") + }; + let transcript = Blake2bTranscript::new(b"dory-assist-test"); + let scalars = dory_verifier_transcript_scalars(&checked, &transcript); + + assert!(matches!( + verify_zk_native_outputs(&inputs.opening, &proof, &scalars), + Err(DoryAssistVerifierError::PublicOutputMismatch { .. }) + )); + } + + #[test] + fn native_final_input_claims_reject_wrong_vector_length() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof_for_checked(&checked); + proof + .claims + .stage1 + .public + .native_final + .inputs + .truncate(NATIVE_FINAL_INPUT_LEN - 1); + let CheckedInputs::Clear(inputs) = checked else { + panic!("fixture is clear") + }; + let transcript = Blake2bTranscript::new(b"dory-assist-test"); + let scalars = dory_verifier_transcript_scalars(&checked, &transcript); + + assert!(matches!( + verify_clear_native_outputs(&inputs.opening, &proof, &scalars), + Err(DoryAssistVerifierError::InvalidProofShape { + component: "claims.stage1.public.native_final.inputs", + .. + }) + )); + } + + #[test] + fn run_stages_returns_stage3_output() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let proof = well_shaped_assist_proof(); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let result = run_stages(&checked, &proof, &mut transcript); + + assert_eq!( + result.ok().map(|output| output.packed_eval), + Some(proof.stages.stage3.packed_eval) + ); + } + + #[test] + fn stage1_output_records_verified_relation_claims() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let proof = well_shaped_assist_proof(); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let stage1 = stages::stage1::verify( + stages::stage1::Stage1Inputs { + checked: &checked, + dimensions: proof.dimensions, + proof: &proof.stages.stage1, + claims: &proof.claims, + }, + &mut transcript, + ) + .expect("stage 1 verifies"); + + assert_eq!( + stage1.relation_outputs.len(), + proof.stages.stage1.relations.len() + ); + assert_eq!(stage1.relation_outputs[0].input_claim, Fq::default()); + assert_eq!( + stage1.relation_outputs[0].sumcheck_final_claim, + stage1.relation_outputs[0].expected_output_claim + ); + assert_eq!(stage1.relation_outputs[0].opening_claims.len(), 5); + assert!(stage1 + .relation_outputs + .iter() + .any(|relation| relation.id == DoryAssistRelationId::G1ScalarMultiplication)); + assert!(stage1 + .relation_outputs + .iter() + .any(|relation| relation.id == DoryAssistRelationId::G1Addition)); + assert!(stage1 + .relation_outputs + .iter() + .any(|relation| relation.id == DoryAssistRelationId::G2ScalarMultiplication)); + assert!(stage1 + .relation_outputs + .iter() + .any(|relation| relation.id == DoryAssistRelationId::G2Addition)); + assert!(stage1 + .relation_outputs + .iter() + .any(|relation| relation.id == DoryAssistRelationId::MillerLoopLineStep)); + assert!(stage1 + .relation_outputs + .iter() + .any(|relation| relation.id == DoryAssistRelationId::MillerLoopLineEvaluation)); + assert!(stage1 + .relation_outputs + .iter() + .any(|relation| relation.id == DoryAssistRelationId::MillerLoopPairProduct)); + assert!(stage1 + .relation_outputs + .iter() + .any(|relation| relation.id == DoryAssistRelationId::MillerLoopAccumulator)); + assert!(stage1 + .relation_outputs + .iter() + .any(|relation| relation.id == DoryAssistRelationId::MillerLoopBoundary)); + assert!(stage1 + .relation_outputs + .iter() + .any(|relation| relation.id == DoryAssistRelationId::DoryReduceScalarFold)); + } + + #[test] + fn stage2_output_records_verified_copy_constraints() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let proof = well_shaped_assist_proof(); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + let stage1 = stages::stage1::verify( + stages::stage1::Stage1Inputs { + checked: &checked, + dimensions: proof.dimensions, + proof: &proof.stages.stage1, + claims: &proof.claims, + }, + &mut transcript, + ) + .expect("stage 1 verifies"); + + let stage2 = stages::stage2::verify( + stages::stage2::Stage2Inputs { + checked: &checked, + dimensions: proof.dimensions, + proof: &proof.stages.stage2, + claims: &proof.claims, + stage1: &stage1, + }, + &mut transcript, + ) + .expect("stage 2 verifies"); + + assert_eq!( + stage2.relation_count as usize, + proof.stages.stage2.copy_constraints.len() + ); + assert_eq!( + stage2.copy_constraints[0].constraint, + proof.stages.stage2.copy_constraints[0] + ); + assert_eq!( + stage2.copy_constraints[0].source_value, + stage2.copy_constraints[0].target_value + ); + assert!(!stage2.dory_reduce_public_folds.is_empty()); + assert_eq!( + stage2.dory_reduce_public_folds[0].expected_value, + stage2.dory_reduce_public_folds[0].target_value + ); + } + + #[test] + fn run_stages_challenge_is_deterministic() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let proof = well_shaped_assist_proof(); + let mut left_transcript = Blake2bTranscript::new(b"dory-assist-test"); + let mut right_transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let left = run_stages(&checked, &proof, &mut left_transcript).expect("left stages verify"); + let right = + run_stages(&checked, &proof, &mut right_transcript).expect("right stages verify"); + + assert_eq!(left.challenge, right.challenge); + } + + #[test] + fn run_stages_rejects_stage1_relation_catalog_mismatch() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof(); + proof.stages.stage1.relations[0].sumcheck.degree += 1; + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let result = run_stages(&checked, &proof, &mut transcript); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::StageClaimMismatch { + stage: DoryAssistStage::Stage1, + .. + }) + )); + } + + #[test] + fn run_stages_rejects_stage1_sumcheck_round_count_mismatch() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof(); + let _ = proof.stages.stage1.relations[0] + .sumcheck_proof + .round_polynomials + .pop(); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let result = run_stages(&checked, &proof, &mut transcript); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::StageSumcheckFailed { + stage: DoryAssistStage::Stage1, + .. + }) + )); + } + + #[test] + fn run_stages_rejects_stage1_sumcheck_degree_bound_mismatch() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof(); + let relation = &mut proof.stages.stage1.relations[0]; + relation.sumcheck_proof.round_polynomials[0] = + CompressedPoly::new(vec![Fq::default(); relation.sumcheck.degree + 1]); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let result = run_stages(&checked, &proof, &mut transcript); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::StageSumcheckFailed { + stage: DoryAssistStage::Stage1, + .. + }) + )); + } + + #[test] + fn run_stages_rejects_stage1_empty_compressed_round() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof(); + proof.stages.stage1.relations[0] + .sumcheck_proof + .round_polynomials[0] = CompressedPoly::new(Vec::new()); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let result = run_stages(&checked, &proof, &mut transcript); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::StageSumcheckFailed { + stage: DoryAssistStage::Stage1, + .. + }) + )); + } + + #[test] + fn run_stages_rejects_stage1_relation_output_mismatch() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof(); + proof.claims.stage1.gt_exponentiation.accumulator = Fq::from_u64(1); + proof.claims.stage1.gt_exponentiation.digit_selector = Fq::from_u64(1); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let result = run_stages(&checked, &proof, &mut transcript); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::StageOutputMismatch { + stage: DoryAssistStage::Stage1, + .. + }) + )); + } + + #[test] + fn run_stages_rejects_stage1_digit_selector_output_mismatch() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof(); + proof + .claims + .stage1 + .gt_exponentiation_digit_selector + .digit_lo = Fq::default(); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let result = run_stages(&checked, &proof, &mut transcript); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::StageOutputMismatch { + stage: DoryAssistStage::Stage1, + .. + }) + )); + } + + #[test] + fn run_stages_rejects_stage1_shift_output_mismatch() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof(); + proof.claims.stage1.gt_exponentiation_shift.accumulator = Fq::from_u64(1); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let result = run_stages(&checked, &proof, &mut transcript); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::StageOutputMismatch { + stage: DoryAssistStage::Stage1, + .. + }) + )); + } + + #[test] + fn run_stages_rejects_stage1_boundary_output_mismatch() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof(); + proof.claims.stage1.gt_exponentiation_boundary.accumulator = Fq::from_u64(1); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let result = run_stages(&checked, &proof, &mut transcript); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::StageOutputMismatch { + stage: DoryAssistStage::Stage1, + .. + }) + )); + } + + #[test] + fn run_stages_rejects_stage1_multiplication_output_mismatch() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof(); + proof.claims.stage1.gt_multiplication.opening.output = Fq::from_u64(1); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let result = run_stages(&checked, &proof, &mut transcript); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::StageOutputMismatch { + stage: DoryAssistStage::Stage1, + .. + }) + )); + } + + #[test] + fn run_stages_rejects_stage1_line_step_output_mismatch() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof(); + proof.claims.stage1.miller_loop.line_step.shifted_state_x[0] = Fq::from_u64(1); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let result = run_stages(&checked, &proof, &mut transcript); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::StageOutputMismatch { + stage: DoryAssistStage::Stage1, + .. + }) + )); + } + + #[test] + fn run_stages_rejects_stage1_line_evaluation_output_mismatch() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof(); + proof + .claims + .stage1 + .miller_loop + .line_evaluation + .line_evaluation_coeffs[6] = Fq::from_u64(1); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let result = run_stages(&checked, &proof, &mut transcript); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::StageOutputMismatch { + stage: DoryAssistStage::Stage1, + .. + }) + )); + } + + #[test] + fn run_stages_rejects_stage2_copy_catalog_mismatch() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let proof = well_shaped_assist_proof(); + let mut changed_proof = proof.clone(); + let _ = changed_proof.stages.stage2.copy_constraints.pop(); + let mut right_transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let _ = run_stages_after_checked_preamble(&checked, &proof); + let right = run_stages(&checked, &changed_proof, &mut right_transcript); + + assert!(matches!( + right, + Err(DoryAssistVerifierError::StageClaimMismatch { + stage: DoryAssistStage::Stage2, + .. + }) + )); + } + + #[test] + fn run_stages_rejects_stage2_copy_value_mismatch() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof(); + proof.claims.stage1.gt_exponentiation_digit_bitness.digit_lo = Fq::default(); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let result = run_stages(&checked, &proof, &mut transcript); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::StageOutputMismatch { + stage: DoryAssistStage::Stage2, + .. + }) + )); + } + + #[test] + fn run_stages_rejects_stage2_line_copy_value_mismatch() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof(); + proof + .claims + .stage1 + .miller_loop + .line_evaluation + .line_coefficients[0][0] = Fq::from_u64(1); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let result = run_stages(&checked, &proof, &mut transcript); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::StageOutputMismatch { + stage: DoryAssistStage::Stage2, + .. + }) + )); + } + + #[test] + fn run_stages_challenge_changes_when_checked_preamble_changes() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut changed_input = fixture.clear_input(); + changed_input.eval += Fr::from_u64(1); + let changed = checked_clear_inputs(changed_input); + let proof = well_shaped_assist_proof(); + + let left = run_stages_after_checked_preamble(&checked, &proof); + let right = run_stages_after_checked_preamble(&changed, &proof); + + assert_ne!(left.challenge, right.challenge); + } + + #[test] + fn run_stages_rejects_empty_stage1_shape() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof(); + proof.stages.stage1.relations.clear(); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let result = run_stages(&checked, &proof, &mut transcript); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::InvalidProofShape { + component: "stage1.relations", + .. + }) + )); + } + + #[test] + fn run_stages_rejects_empty_stage2_shape() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof(); + proof.stages.stage2.copy_constraints.clear(); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let result = run_stages(&checked, &proof, &mut transcript); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::InvalidProofShape { + component: "stage2.copy_constraints", + .. + }) + )); + } + + #[test] + fn run_stages_rejects_empty_stage3_opening_row() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof(); + proof.opening_proof.combined_row.clear(); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let result = run_stages(&checked, &proof, &mut transcript); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::InvalidProofShape { + component: "stage3.opening_proof.combined_row", + .. + }) + )); + } + + #[test] + fn run_stages_rejects_empty_stage3_packed_point() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof(); + proof.claims.opening.packed_point.clear(); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let result = run_stages(&checked, &proof, &mut transcript); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::InvalidProofShape { + component: "stage3.claims.opening.packed_point", + .. + }) + )); + } + + #[test] + fn run_stages_rejects_empty_stage3_dense_commitment() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof(); + proof.dense_commitment.rows.clear(); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let result = run_stages(&checked, &proof, &mut transcript); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::InvalidProofShape { + component: "stage3.dense_commitment.rows", + .. + }) + )); + } + + #[test] + fn run_stages_rejects_stage3_packed_eval_mismatch() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof(); + proof.stages.stage3.packed_eval += Fq::from_u64(1); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let result = run_stages(&checked, &proof, &mut transcript); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::StageOutputMismatch { + stage: DoryAssistStage::Stage3, + .. + }) + )); + } + + #[test] + fn run_stages_rejects_non_power_of_two_hyrax_row_count() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof(); + proof.dense_commitment.rows.push(Default::default()); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let result = run_stages(&checked, &proof, &mut transcript); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::InvalidProofShape { + component: "stage3.dense_commitment.rows", + .. + }) + )); + } + + #[test] + fn run_stages_rejects_non_power_of_two_hyrax_row_len() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof(); + proof.opening_proof.combined_row.push(Fq::from_u64(37)); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let result = run_stages(&checked, &proof, &mut transcript); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::InvalidProofShape { + component: "stage3.opening_proof.combined_row", + .. + }) + )); + } + + #[test] + fn run_stages_rejects_hyrax_dimension_mismatch() { + let fixture = dory_opening_fixture(); + let checked = checked_clear_inputs(fixture.clear_input()); + let mut proof = well_shaped_assist_proof(); + proof.claims.opening.packed_point.push(Fq::from_u64(41)); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + + let result = run_stages(&checked, &proof, &mut transcript); + + assert!(matches!( + result, + Err(DoryAssistVerifierError::InvalidProofShape { + component: "stage3.hyrax_dimensions", + .. + }) + )); + } + + struct DoryOpeningFixture { + verifier_setup: DoryVerifierSetup, + proof: DoryProof, + commitment: DoryCommitment, + point: Vec, + eval: Fr, + } + + impl DoryOpeningFixture { + fn clear_input(&self) -> PcsAssistClearInput<'_, DoryScheme> { + PcsAssistClearInput { + setup: &self.verifier_setup, + pcs_proof: &self.proof, + commitment: &self.commitment, + point: &self.point, + eval: self.eval, + } + } + + fn zk_input(&self) -> PcsAssistZkInput<'_, DoryScheme> { + PcsAssistZkInput { + setup: &self.verifier_setup, + pcs_proof: &self.proof, + commitment: &self.commitment, + point: &self.point, + } + } + } + + fn assert_clear_opening_matches( + opening: &ClearOpeningStatement<'_>, + fixture: &DoryOpeningFixture, + ) { + assert_zk_opening_matches( + &ZkOpeningStatement { + setup: opening.setup, + pcs_proof: opening.pcs_proof, + commitment: opening.commitment, + point: opening.point, + }, + fixture, + ); + assert_eq!(opening.eval, fixture.eval); + } + + fn assert_zk_opening_matches(opening: &ZkOpeningStatement<'_>, fixture: &DoryOpeningFixture) { + assert!(std::ptr::eq( + opening.setup, + std::ptr::from_ref(&fixture.verifier_setup) + )); + assert_eq!(opening.pcs_proof, &fixture.proof); + assert_eq!(opening.commitment, &fixture.commitment); + assert_eq!(opening.point, fixture.point.as_slice()); + } + + fn dory_opening_fixture() -> DoryOpeningFixture { + dory_opening_fixture_with_shift(0) + } + + fn dory_zk_opening_fixture() -> DoryOpeningFixture { + dory_zk_opening_fixture_with_shift(0) + } + + fn dory_opening_fixture_with_shift(shift: u64) -> DoryOpeningFixture { + dory_opening_fixture_with_num_vars_and_shift(2, shift) + } + + fn dory_opening_fixture_with_num_vars(num_vars: usize) -> DoryOpeningFixture { + dory_opening_fixture_with_num_vars_and_shift(num_vars, 0) + } + + fn dory_opening_fixture_with_num_vars_and_shift( + num_vars: usize, + shift: u64, + ) -> DoryOpeningFixture { + let (prover_setup, verifier_setup) = DoryScheme::setup(num_vars); + let offset = Fr::from_u64(shift); + let poly = Polynomial::::from( + (0..(1usize << num_vars)) + .map(|i| Fr::from_u64(u64::try_from(i + 1).expect("fixture index fits")) + offset) + .collect::>(), + ); + let point = (0..num_vars) + .map(|i| Fr::from_u64(u64::try_from(5 + 2 * i).expect("fixture point fits")) + offset) + .collect::>(); + let eval = poly.evaluate(&point); + let (commitment, hint) = DoryScheme::commit(poly.evaluations(), &prover_setup); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + let proof = DoryScheme::open( + &poly, + &point, + eval, + &prover_setup, + Some(hint), + &mut transcript, + ); + + DoryOpeningFixture { + verifier_setup, + proof, + commitment, + point, + eval, + } + } + + fn dory_zk_opening_fixture_with_shift(shift: u64) -> DoryOpeningFixture { + let num_vars = 2; + let (prover_setup, verifier_setup) = DoryScheme::setup(num_vars); + let offset = Fr::from_u64(shift); + let poly = Polynomial::::from(vec![ + Fr::from_u64(1) + offset, + Fr::from_u64(2) + offset, + Fr::from_u64(3) + offset, + Fr::from_u64(4) + offset, + ]); + let point = vec![Fr::from_u64(5) + offset, Fr::from_u64(7) + offset]; + let eval = poly.evaluate(&point); + let (commitment, hint) = + ::commit_zk(poly.evaluations(), &prover_setup); + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + let (proof, _hiding_commitment, _blind) = + DoryScheme::open_zk(&poly, &point, eval, &prover_setup, hint, &mut transcript); + + DoryOpeningFixture { + verifier_setup, + proof, + commitment, + point, + eval, + } + } + + fn checked_input_test_challenge(checked: &CheckedInputs<'_>) -> Fq { + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + let _ = absorb_checked_inputs_for_test(checked, &mut transcript); + squeeze_fq_challenge(&mut transcript, b"checked_test_challenge") + } + + fn run_stages_after_checked_preamble( + checked: &CheckedInputs<'_>, + proof: &DoryAssistProof, + ) -> Stage3Output { + try_run_stages_after_checked_preamble(checked, proof).expect("stages verify") + } + + fn try_run_stages_after_checked_preamble( + checked: &CheckedInputs<'_>, + proof: &DoryAssistProof, + ) -> Result { + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + let _ = absorb_checked_inputs_for_test(checked, &mut transcript); + let _ = squeeze_checked_input_digest(&mut transcript); + run_stages(checked, proof, &mut transcript) + } + + #[expect( + clippy::expect_used, + reason = "test fixture dimensions are derived from checked Dory inputs" + )] + fn dory_assist_dimensions_for_checked(checked: &CheckedInputs<'_>) -> DoryAssistDimensions { + let supported = default_dory_assist_dimensions(); + let unpacked = DoryAssistDimensions::new( + supported.gt, + supported.g1, + supported.g2, + supported.miller_loop, + DoryReduceDimensions::new( + checked.point().len(), + checked.pcs_proof().reduce_round_count(), + ), + supported.wiring, + PrefixPackingDimensions::new(0, 0, 0).expect("valid empty packing dimensions"), + ); + let packing = composition::prefix_packing_catalog(unpacked) + .minimal_dimensions() + .expect("valid checked Dory-assist packing dimensions"); + + DoryAssistDimensions::new( + unpacked.gt, + unpacked.g1, + unpacked.g2, + unpacked.miller_loop, + unpacked.dory_reduce, + unpacked.wiring, + packing, + ) + } + + fn well_shaped_assist_proof() -> DoryAssistProof { + well_shaped_assist_proof_with_dimensions(default_dory_assist_dimensions()) + } + + fn well_shaped_assist_proof_with_dimensions( + dimensions: DoryAssistDimensions, + ) -> DoryAssistProof { + let mut proof = DoryAssistProof { + dimensions, + ..DoryAssistProof::default() + }; + proof + .claims + .stage1 + .public + .input + .dory_proof_artifacts + .resize( + DoryProofArtifactLayout::new(proof.dimensions.dory_reduce.reduce_rounds()) + .expected_len(), + Fq::default(), + ); + proof + .claims + .stage1 + .public + .input + .verifier_setup_artifacts + .resize( + setup_artifacts::dory_setup_artifact_count( + proof.dimensions.dory_reduce.reduce_rounds(), + ), + Fq::default(), + ); + proof.claims.stage1.public.input.transcript_scalars.resize( + transcript_scalars::dory_reduce_s2_fold_factor( + proof.dimensions.dory_reduce.point_len(), + 0, + ) + 1, + Fq::default(), + ); + proof + .claims + .stage1 + .public + .input + .jolt_commitments + .resize(1 + GT_ARTIFACT_COEFFS, Fq::default()); + proof.stages.stage1 = + stages::stage1::Stage1Proof::canonical_for_dimensions(proof.dimensions); + proof.stages.stage2 = + stages::stage2::Stage2Proof::canonical_for_dimensions(proof.dimensions); + bind_zero_dory_reduce_fixture(&mut proof); + for constraint in dory_reduce::initial_state_copy_constraints() { + bind_dory_reduce_copy_target(&mut proof, constraint); + } + populate_valid_hyrax_opening(&mut proof); + proof + } + + fn well_shaped_assist_proof_for_checked(checked: &CheckedInputs<'_>) -> DoryAssistProof { + let mut proof = + well_shaped_assist_proof_with_dimensions(dory_assist_dimensions_for_checked(checked)); + proof.claims.stage1.public.input = checked_input_public_claims_for_test(checked); + bind_public_input_copy_fixture(&mut proof); + bind_native_public_output_fixture(&mut proof, checked); + proof + } + + fn bind_zero_dory_reduce_fixture(proof: &mut DoryAssistProof) { + proof.claims.stage1.dory_reduce.transitions.clear(); + proof.claims.stage1.dory_reduce.state_chain.clear(); + proof.claims.stage1.dory_reduce.boundary.clear(); + let protocol = protocol_claims::(proof.dimensions); + for relation_id in [ + DoryAssistRelationId::DoryReduceGtTransition, + DoryAssistRelationId::DoryReduceG1Transition, + DoryAssistRelationId::DoryReduceG2Transition, + ] { + let relation = protocol + .relation(relation_id) + .expect("Dory-reduce transition relation is in the protocol catalog"); + proof.claims.stage1.dory_reduce.transitions.extend( + relation + .required_openings() + .into_iter() + .map(|id| DoryAssistOpeningClaim { + id, + value: Fq::default(), + }), + ); + } + if proof.dimensions.dory_reduce.reduce_rounds() > 1 { + for relation_id in [ + DoryAssistRelationId::DoryReduceStateChain, + DoryAssistRelationId::DoryReduceBoundary, + ] { + let relation = protocol + .relation(relation_id) + .expect("Dory-reduce multi-round relation is in the protocol catalog"); + let claims = + relation + .required_openings() + .into_iter() + .map(|id| DoryAssistOpeningClaim { + id, + value: Fq::default(), + }); + match relation_id { + DoryAssistRelationId::DoryReduceStateChain => { + proof.claims.stage1.dory_reduce.state_chain.extend(claims); + } + DoryAssistRelationId::DoryReduceBoundary => { + proof.claims.stage1.dory_reduce.boundary.extend(claims); + } + _ => unreachable!("only multi-round Dory-reduce relations are handled here"), + } + } + } + } + + fn bind_public_input_copy_fixture(proof: &mut DoryAssistProof) { + let vmv_c0 = proof.claims.stage1.public.input.dory_proof_artifacts[DORY_VMV_C_START]; + proof.claims.stage1.gt_exponentiation.accumulator = vmv_c0; + proof.claims.stage1.gt_exponentiation_shift.accumulator = vmv_c0; + proof.claims.stage1.gt_exponentiation_boundary.accumulator = vmv_c0; + proof.claims.stage1.public.gt_shift_eq_kernel = Fq::default(); + proof + .claims + .stage1 + .public + .gt_exponentiation_boundary + .initial_value = vmv_c0; + proof.claims.stage1.miller_loop.line_evaluation.g1_point_x = + proof.claims.stage1.public.input.dory_proof_artifacts[DORY_VMV_E1_START]; + proof.claims.stage1.miller_loop.line_evaluation.g1_point_y = + proof.claims.stage1.public.input.dory_proof_artifacts[DORY_VMV_E1_START + 1]; + } + + fn bind_native_public_output_fixture(proof: &mut DoryAssistProof, checked: &CheckedInputs<'_>) { + let transcript = Blake2bTranscript::new(b"dory-assist-test"); + let scalars = dory_verifier_transcript_scalars(checked, &transcript); + match checked { + CheckedInputs::Clear(inputs) => { + let native_final_inputs = + transparent_native_final_input_claims(&inputs.opening, &scalars) + .expect("transparent native-final inputs are well shaped"); + proof + .claims + .stage1 + .public + .native_final + .bind(native_final_inputs); + proof.public_outputs.pre_final_exponentiation = + transparent_replayed_final_pairing_check(&inputs.opening, &scalars) + .expect("transparent final fixture is well shaped") + .pre_final_exponentiation(); + } + CheckedInputs::Zk(inputs) => { + let native_final_inputs = zk_native_final_input_claims(&inputs.opening, &scalars) + .expect("ZK native-final inputs are well shaped"); + proof + .claims + .stage1 + .public + .native_final + .bind(native_final_inputs); + proof.public_outputs.pre_final_exponentiation = + zk_replayed_final_pairing_check(&inputs.opening, &scalars) + .expect("ZK final fixture is well shaped") + .pre_final_exponentiation(); + } + } + + let output_coefficients = proof.public_outputs.pre_final_exponentiation_coefficients(); + proof + .claims + .stage1 + .public + .miller_loop + .bind_pre_final_exponentiation(&proof.public_outputs); + proof + .claims + .stage1 + .public + .miller_loop + .boundary_initial_value = output_coefficients; + proof.claims.stage1.miller_loop.accumulator.accumulator = output_coefficients; + proof + .claims + .stage1 + .miller_loop + .accumulator + .shifted_accumulator = output_coefficients; + proof.claims.stage1.miller_loop.boundary.accumulator = output_coefficients; + proof.claims.stage1.miller_loop.boundary.shifted_accumulator = output_coefficients; + + let square_row = + &mut proof.claims.stage1.gt_multiplication.rows[composition::ACCUMULATOR_SQUARE_GT_ROW]; + square_row.left = output_coefficients; + square_row.right = output_coefficients; + square_row.output = output_coefficients; + + let mul_row = + &mut proof.claims.stage1.gt_multiplication.rows[composition::ACCUMULATOR_MUL_GT_ROW]; + mul_row.left = output_coefficients; + mul_row.output = output_coefficients; + + proof + .claims + .stage1 + .public + .miller_loop + .accumulator_shift_eq_kernel = accumulator_zero_sumcheck_kernel(proof, checked); + bind_dory_reduce_transition_copy_fixture(proof, checked); + populate_valid_hyrax_opening(proof); + } + + fn tamper_native_final_c_acc(proof: &mut DoryAssistProof) { + let inputs = &mut proof.claims.stage1.public.native_final.inputs; + let identity = Bn254GT::identity().fq12_coefficients(); + let current_is_identity = inputs + [NATIVE_FINAL_GT_C_START..NATIVE_FINAL_GT_C_START + Bn254GT::FQ12_COEFFICIENTS] + .iter() + .copied() + .eq(identity); + if current_is_identity { + inputs.copy_within( + NATIVE_FINAL_D1_START..NATIVE_FINAL_D1_START + GT_ARTIFACT_COEFFS, + NATIVE_FINAL_GT_C_START, + ); + return; + } + + inputs[NATIVE_FINAL_GT_C_START..NATIVE_FINAL_GT_C_START + Bn254GT::FQ12_COEFFICIENTS] + .copy_from_slice(&identity); + inputs[NATIVE_FINAL_GT_C_START + Bn254GT::FQ12_COEFFICIENTS + ..NATIVE_FINAL_GT_C_START + GT_ARTIFACT_COEFFS] + .fill(Fq::default()); + } + + fn bind_dory_reduce_transition_copy_fixture( + proof: &mut DoryAssistProof, + checked: &CheckedInputs<'_>, + ) { + for constraint in dory_reduce::initial_state_copy_constraints() { + bind_dory_reduce_copy_target(proof, constraint); + } + if proof.dimensions.dory_reduce.reduce_rounds() == 1 { + for constraint in dory_reduce_transition_copy_constraints(proof.dimensions) { + bind_dory_reduce_copy_target(proof, constraint); + } + } + + bind_dory_reduce_public_fold_fixture( + proof, + checked, + DoryAssistRelationId::DoryReduceGtTransition, + ); + rebalance_dory_reduce_transition_relation( + proof, + checked, + DoryAssistRelationId::DoryReduceGtTransition, + ); + bind_dory_reduce_public_fold_fixture( + proof, + checked, + DoryAssistRelationId::DoryReduceG1Transition, + ); + rebalance_dory_reduce_transition_relation( + proof, + checked, + DoryAssistRelationId::DoryReduceG1Transition, + ); + bind_dory_reduce_public_fold_fixture( + proof, + checked, + DoryAssistRelationId::DoryReduceG2Transition, + ); + rebalance_dory_reduce_transition_relation( + proof, + checked, + DoryAssistRelationId::DoryReduceG2Transition, + ); + bind_dory_reduce_public_fold_fixture( + proof, + checked, + DoryAssistRelationId::DoryReduceScalarFold, + ); + rebalance_dory_reduce_scalar_fold_relation(proof, checked); + if proof.dimensions.dory_reduce.reduce_rounds() > 1 { + bind_dory_reduce_boundary_fixture(proof); + } + } + + fn dory_reduce_transition_copy_constraints( + dimensions: jolt_claims::protocols::dory_assist::DoryAssistDimensions, + ) -> Vec { + dory_reduce::proof_artifact_copy_constraints(0) + .into_iter() + .chain(dory_reduce::round_setup_artifact_copy_constraints( + dimensions.dory_reduce.reduce_rounds(), + 0, + )) + .chain(dory_reduce::transition_transcript_scalar_copy_constraints( + dimensions.dory_reduce.point_len(), + 0, + )) + .collect() + } + + fn bind_dory_reduce_copy_target( + proof: &mut DoryAssistProof, + constraint: DoryAssistCopyConstraint, + ) { + let value = match constraint.source { + DoryAssistValueRef::Public { id, .. } => proof + .claims + .stage1 + .public + .claim(&id) + .expect("fixture public claim exists"), + DoryAssistValueRef::Constant(value) => Fq::from_u64(value as u64), + DoryAssistValueRef::Witness { .. } | DoryAssistValueRef::Challenge(_) => { + panic!("Dory-reduce fixture copy source must be public or constant") + } + }; + let opening = constraint + .target + .witness_opening() + .expect("Dory-reduce transition fixture copy target must be witness"); + set_dory_reduce_opening(proof, opening, value); + } + + #[expect( + clippy::expect_used, + reason = "test fixture public-fold sources are derived from canonical Dory-reduce dimensions" + )] + fn bind_dory_reduce_public_fold_fixture( + proof: &mut DoryAssistProof, + checked: &CheckedInputs<'_>, + relation_id: DoryAssistRelationId, + ) { + let context = stage1_relation_context_for_test(proof, checked, relation_id); + let weights = EqPolynomial::new(context.sumcheck_point).evaluations(); + for constraint in dory_reduce::public_fold_constraints(proof.dimensions.dory_reduce) { + let opening = constraint + .target + .witness_opening() + .expect("Dory-reduce public fold target is a witness opening"); + if opening_relation(opening) != relation_id { + continue; + } + assert!( + constraint.sources.len() <= weights.len(), + "public fold sources fit the relation point domain" + ); + let value = + constraint + .sources + .iter() + .zip(&weights) + .fold(Fq::default(), |acc, (id, weight)| { + let public = proof + .claims + .stage1 + .public_claim(id) + .expect("fixture public-fold source exists"); + acc + public * *weight + }); + set_dory_reduce_opening(proof, opening, value); + } + } + + fn bind_dory_reduce_boundary_fixture(proof: &mut DoryAssistProof) { + for term in dory_reduce::initial_boundary_terms() + .into_iter() + .chain(dory_reduce::final_boundary_terms()) + { + let value = match term.value { + dory_reduce::DoryReduceBoundaryValue::ConstantOne => Fq::from_u64(1), + dory_reduce::DoryReduceBoundaryValue::Public(id) => proof + .claims + .stage1 + .public_claim(&id) + .expect("fixture Dory-reduce boundary public claim exists"), + }; + set_dory_reduce_opening(proof, term.opening, value); + } + } + + fn rebalance_dory_reduce_transition_relation( + proof: &mut DoryAssistProof, + checked: &CheckedInputs<'_>, + relation_id: DoryAssistRelationId, + ) { + let protocol = protocol_claims::(proof.dimensions); + let relation = protocol + .relation(relation_id) + .expect("Dory-reduce transition relation is in the protocol catalog"); + let context = stage1_relation_context_for_test(proof, checked, relation_id); + let target = dory_reduce_transition_target_opening(relation_id); + let input = relation + .input + .expression() + .try_evaluate( + |id| { + proof + .claims + .stage1 + .opening_claim(id) + .ok_or("missing opening") + }, + |id| resolve_test_challenge(&context.relation_challenges, id), + |id| proof.claims.stage1.public_claim(id).ok_or("missing public"), + ) + .expect("fixture Dory-reduce transition input evaluates"); + let output = relation + .output + .expression() + .try_evaluate( + |id| { + proof + .claims + .stage1 + .opening_claim(id) + .ok_or("missing opening") + }, + |id| resolve_test_challenge(&context.relation_challenges, id), + |id| proof.claims.stage1.public_claim(id).ok_or("missing public"), + ) + .expect("fixture Dory-reduce transition output evaluates"); + let factor = sumcheck_linear_factor(&context.sumcheck_point); + let delta = (output - input * factor) + * factor + .inverse() + .expect("fixture Dory-reduce sumcheck factor is nonzero"); + set_dory_reduce_opening( + proof, + target, + get_dory_reduce_opening(proof, target) + delta, + ); + } + + fn rebalance_dory_reduce_scalar_fold_relation( + proof: &mut DoryAssistProof, + checked: &CheckedInputs<'_>, + ) { + let protocol = protocol_claims::(proof.dimensions); + let relation = protocol + .relation(DoryAssistRelationId::DoryReduceScalarFold) + .expect("Dory-reduce scalar-fold relation is in the protocol catalog"); + let context = stage1_relation_context_for_test( + proof, + checked, + DoryAssistRelationId::DoryReduceScalarFold, + ); + let input = relation + .input + .expression() + .try_evaluate( + |id| { + proof + .claims + .stage1 + .opening_claim(id) + .ok_or("missing opening") + }, + |id| resolve_test_challenge(&context.relation_challenges, id), + |id| proof.claims.stage1.public_claim(id).ok_or("missing public"), + ) + .expect("fixture Dory-reduce scalar-fold input evaluates"); + let output = relation + .output + .expression() + .try_evaluate( + |id| { + proof + .claims + .stage1 + .opening_claim(id) + .ok_or("missing opening") + }, + |id| resolve_test_challenge(&context.relation_challenges, id), + |id| proof.claims.stage1.public_claim(id).ok_or("missing public"), + ) + .expect("fixture Dory-reduce scalar-fold output evaluates"); + let factor = sumcheck_linear_factor(&context.sumcheck_point); + let target = dory_reduce::s1_next_accumulator_opening(); + let delta = (output - input * factor) + * factor + .inverse() + .expect("fixture Dory-reduce scalar-fold sumcheck factor is nonzero"); + set_dory_reduce_opening( + proof, + target, + get_dory_reduce_opening(proof, target) + delta, + ); + } + + fn dory_reduce_transition_target_opening( + relation: DoryAssistRelationId, + ) -> DoryAssistOpeningId { + let polynomial = match relation { + DoryAssistRelationId::DoryReduceGtTransition => DoryReducePolynomial::NextC(0), + DoryAssistRelationId::DoryReduceG1Transition => DoryReducePolynomial::NextE1X, + DoryAssistRelationId::DoryReduceG2Transition => DoryReducePolynomial::NextE2X0, + _ => panic!("not a Dory-reduce transition relation"), + }; + dory_reduce_opening(relation, polynomial) + } + + fn dory_reduce_opening( + relation: DoryAssistRelationId, + polynomial: DoryReducePolynomial, + ) -> DoryAssistOpeningId { + DoryAssistOpeningId::virtual_polynomial( + DoryAssistVirtualPolynomial::DoryReduce(polynomial), + relation, + ) + } + + fn set_dory_reduce_transition_opening( + proof: &mut DoryAssistProof, + opening: DoryAssistOpeningId, + value: Fq, + ) { + let claim = proof + .claims + .stage1 + .dory_reduce + .transitions + .iter_mut() + .find(|claim| claim.id == opening) + .expect("fixture contains Dory-reduce transition opening"); + claim.value = value; + } + + fn set_dory_reduce_opening_claim( + claims: &mut [DoryAssistOpeningClaim], + opening: DoryAssistOpeningId, + value: Fq, + ) { + let claim = claims + .iter_mut() + .find(|claim| claim.id == opening) + .expect("fixture contains Dory-reduce relation opening"); + claim.value = value; + } + + fn set_dory_reduce_opening( + proof: &mut DoryAssistProof, + opening: DoryAssistOpeningId, + value: Fq, + ) { + if opening == dory_reduce::s1_accumulator_opening() { + proof.claims.stage1.dory_reduce.scalar_fold.s1_accumulator = value; + } else if opening == dory_reduce::s1_next_accumulator_opening() { + proof + .claims + .stage1 + .dory_reduce + .scalar_fold + .s1_next_accumulator = value; + } else if opening == dory_reduce::s1_fold_factor_opening() { + proof.claims.stage1.dory_reduce.scalar_fold.s1_fold_factor = value; + } else if opening == dory_reduce::s2_accumulator_opening() { + proof.claims.stage1.dory_reduce.scalar_fold.s2_accumulator = value; + } else if opening == dory_reduce::s2_next_accumulator_opening() { + proof + .claims + .stage1 + .dory_reduce + .scalar_fold + .s2_next_accumulator = value; + } else if opening == dory_reduce::s2_fold_factor_opening() { + proof.claims.stage1.dory_reduce.scalar_fold.s2_fold_factor = value; + } else { + match opening_relation(opening) { + DoryAssistRelationId::DoryReduceGtTransition + | DoryAssistRelationId::DoryReduceG1Transition + | DoryAssistRelationId::DoryReduceG2Transition => { + set_dory_reduce_transition_opening(proof, opening, value); + } + DoryAssistRelationId::DoryReduceStateChain => set_dory_reduce_opening_claim( + &mut proof.claims.stage1.dory_reduce.state_chain, + opening, + value, + ), + DoryAssistRelationId::DoryReduceBoundary => set_dory_reduce_opening_claim( + &mut proof.claims.stage1.dory_reduce.boundary, + opening, + value, + ), + relation => panic!("fixture cannot set non-Dory-reduce opening {relation:?}"), + } + } + } + + fn get_dory_reduce_opening(proof: &DoryAssistProof, opening: DoryAssistOpeningId) -> Fq { + proof + .claims + .stage1 + .opening_claim(&opening) + .expect("fixture Dory-reduce opening exists") + } + + fn opening_relation(opening: DoryAssistOpeningId) -> DoryAssistRelationId { + let DoryAssistOpeningId::Polynomial { relation, .. } = opening; + relation + } + + fn checked_input_public_claims_for_test( + checked: &CheckedInputs<'_>, + ) -> DoryAssistInputPublicClaims { + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + let mut input_public_claims = absorb_checked_inputs_for_test(checked, &mut transcript); + input_public_claims.checked_input_digest = squeeze_checked_input_digest(&mut transcript); + input_public_claims + } + + fn accumulator_zero_sumcheck_kernel( + proof: &DoryAssistProof, + checked: &CheckedInputs<'_>, + ) -> Fq { + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + let _ = absorb_checked_inputs_for_test(checked, &mut transcript); + let _ = squeeze_checked_input_digest(&mut transcript); + absorb_stage1_preamble_for_test( + checked.mode_name().as_bytes(), + proof.stages.stage1.relation_count(), + &mut transcript, + ); + + let protocol = protocol_claims::(proof.dimensions); + for relation in &proof.stages.stage1.relations { + let relation_claims = protocol + .relation(relation.id) + .expect("stage 1 relation belongs to Dory-assist protocol"); + absorb_stage1_relation_for_test(relation.id, &relation.sumcheck, &mut transcript); + let relation_challenges = relation_claims + .required_challenges() + .into_iter() + .map(|id| (id, squeeze_fq(&mut transcript))) + .collect::>(); + + if relation.id == DoryAssistRelationId::MillerLoopAccumulator { + let input_claim = relation_claims + .input + .expression() + .try_evaluate( + |id| { + proof + .claims + .stage1 + .opening_claim(id) + .ok_or("missing opening") + }, + |id| resolve_test_challenge(&relation_challenges, id), + |id| proof.claims.stage1.public_claim(id).ok_or("missing public"), + ) + .expect("fixture accumulator input evaluates"); + let final_claim = relation.sumcheck_proof.round_polynomials.iter().fold( + input_claim, + |running_sum, round_proof| { + absorb_sumcheck_round_for_test(round_proof, &mut transcript); + let challenge = squeeze_fq(&mut transcript); + round_proof.evaluate_with_hint(running_sum, challenge) + }, + ); + return final_claim + * input_claim + .inverse() + .expect("native-output fixture has nonzero accumulator claim"); + } + + for round_proof in &relation.sumcheck_proof.round_polynomials { + absorb_sumcheck_round_for_test(round_proof, &mut transcript); + let _ = squeeze_fq(&mut transcript); + } + for id in relation_claims.required_openings() { + let value = proof + .claims + .stage1 + .opening_claim(&id) + .expect("fixture has canonical opening claim"); + transcript.append_labeled(b"opening_claim", &value); + } + } + + panic!("canonical Stage 1 relation catalog has no Miller-loop accumulator relation"); + } + + struct Stage1RelationContextForTest { + relation_challenges: Vec<(DoryAssistChallengeId, Fq)>, + sumcheck_point: Vec, + } + + fn stage1_relation_context_for_test( + proof: &DoryAssistProof, + checked: &CheckedInputs<'_>, + target: DoryAssistRelationId, + ) -> Stage1RelationContextForTest { + let mut transcript = Blake2bTranscript::new(b"dory-assist-test"); + let _ = absorb_checked_inputs_for_test(checked, &mut transcript); + let _ = squeeze_checked_input_digest(&mut transcript); + absorb_stage1_preamble_for_test( + checked.mode_name().as_bytes(), + proof.stages.stage1.relation_count(), + &mut transcript, + ); + + let protocol = protocol_claims::(proof.dimensions); + for relation in &proof.stages.stage1.relations { + let relation_claims = protocol + .relation(relation.id) + .expect("stage 1 relation belongs to Dory-assist protocol"); + absorb_stage1_relation_for_test(relation.id, &relation.sumcheck, &mut transcript); + let relation_challenges = relation_claims + .required_challenges() + .into_iter() + .map(|id| (id, squeeze_fq(&mut transcript))) + .collect::>(); + + if relation.id == target { + let mut sumcheck_point = Vec::with_capacity(relation.sumcheck.rounds); + for round_proof in &relation.sumcheck_proof.round_polynomials { + absorb_sumcheck_round_for_test(round_proof, &mut transcript); + sumcheck_point.push(squeeze_fq(&mut transcript)); + } + return Stage1RelationContextForTest { + relation_challenges, + sumcheck_point, + }; + } + + for round_proof in &relation.sumcheck_proof.round_polynomials { + absorb_sumcheck_round_for_test(round_proof, &mut transcript); + let _ = squeeze_fq(&mut transcript); + } + for id in relation_claims.required_openings() { + let value = proof + .claims + .stage1 + .opening_claim(&id) + .expect("fixture has canonical opening claim"); + transcript.append_labeled(b"opening_claim", &value); + } + } + + panic!("target relation {target:?} is absent from the canonical Stage 1 catalog"); + } + + fn sumcheck_linear_factor(point: &[Fq]) -> Fq { + point + .iter() + .copied() + .fold(Fq::from_u64(1), |acc, challenge| acc * challenge) + } + + fn resolve_test_challenge( + challenges: &[(DoryAssistChallengeId, Fq)], + id: &DoryAssistChallengeId, + ) -> Result { + challenges + .iter() + .find(|(candidate, _)| candidate == id) + .map(|(_, value)| *value) + .ok_or("missing challenge") + } + + fn absorb_stage1_preamble_for_test( + mode_name: &'static [u8], + relation_count: u32, + transcript: &mut Blake2bTranscript, + ) { + transcript.append(&Label(b"dory_assist_stage1")); + transcript.append(&Label(mode_name)); + transcript.append(&Label(b"stage1_relations")); + transcript.append(&U64Word(relation_count as u64)); + } + + fn absorb_stage1_relation_for_test( + id: DoryAssistRelationId, + sumcheck: &DoryAssistSumcheckSpec, + transcript: &mut Blake2bTranscript, + ) { + transcript.append(&Label(b"stage1_relation_id")); + transcript.append(&U64Word(relation_transcript_tag_for_test(id) as u64)); + transcript.append(&Label(b"stage1_sumcheck_domain")); + transcript.append(&U64Word(0)); + transcript.append(&Label(b"stage1_sumcheck_rounds")); + transcript.append(&U64Word(sumcheck.rounds as u64)); + transcript.append(&Label(b"stage1_sumcheck_degree")); + transcript.append(&U64Word(sumcheck.degree as u64)); + } + + fn absorb_sumcheck_round_for_test( + round_proof: &CompressedPoly, + transcript: &mut Blake2bTranscript, + ) { + let coeffs = round_proof.coeffs_except_linear_term(); + transcript.append(&LabelWithCount( + SUMCHECK_ROUND_TRANSCRIPT_LABEL, + coeffs.len() as u64, + )); + for coeff in coeffs { + transcript.append(coeff); + } + } + + fn relation_transcript_tag_for_test(id: DoryAssistRelationId) -> usize { + CANONICAL_RELATION_ORDER + .iter() + .position(|candidate| *candidate == id) + .expect("stage 1 relation has a canonical transcript tag") + } + + fn populate_valid_hyrax_opening(proof: &mut DoryAssistProof) { + let reduced_claims = reduced_opening_claims(proof); + let poly_len = reduced_claims.len().next_power_of_two(); + let num_vars = poly_len.trailing_zeros() as usize; + let row_vars = num_vars / 2; + let col_vars = num_vars - row_vars; + let dimensions = + HyraxDimensions::new(num_vars, row_vars, col_vars).expect("valid Hyrax dimensions"); + let hyrax_setup = derive_hyrax_prover_setup(dimensions).expect("seed-derived Hyrax setup"); + let mut evaluations = vec![Fq::default(); poly_len]; + for (slot, claim) in evaluations.iter_mut().zip(&reduced_claims) { + *slot = claim.value; + } + let packed_poly = Polynomial::::from(evaluations); + let packed_point = (0..num_vars) + .map(|index| Fq::from_u64(13 + 6 * index as u64)) + .collect::>(); + let packed_eval = packed_poly.evaluate(&packed_point); + let (dense_commitment, hint) = DoryAssistHyrax::commit(&packed_poly, &hyrax_setup); + let mut transcript = Blake2bTranscript::new(b"dory-assist-hyrax-unit-fixture"); + let opening_proof = DoryAssistHyrax::open( + &packed_poly, + &packed_point, + packed_eval, + &hyrax_setup, + Some(hint), + &mut transcript, + ); + + proof.stages.stage3.packed_eval = packed_eval; + proof.stages.stage3.reduced_openings = + reduced_claims.iter().map(|claim| claim.id).collect(); + proof.claims.opening.packed_point = packed_point; + proof.claims.opening.packed_eval = packed_eval; + proof.opening_proof = opening_proof; + proof.dense_commitment = dense_commitment; + } + + fn reduced_opening_claims(proof: &DoryAssistProof) -> Vec { + let protocol = protocol_claims::(proof.dimensions); + let mut reduced_claims = Vec::new(); + for relation in &proof.stages.stage1.relations { + let relation_claims = protocol + .relation(relation.id) + .expect("stage 1 relation belongs to Dory-assist protocol"); + for id in relation_claims.required_openings() { + if reduced_claims + .iter() + .any(|claim: &DoryAssistOpeningClaim| claim.id == id) + { + continue; + } + let value = proof + .claims + .stage1 + .opening_claim(&id) + .expect("stage 1 claim value exists for canonical opening"); + reduced_claims.push(DoryAssistOpeningClaim { id, value }); + } + } + reduced_claims + } +} diff --git a/crates/jolt-dory-assist-verifier/tests/completeness.rs b/crates/jolt-dory-assist-verifier/tests/completeness.rs new file mode 100644 index 0000000000..ba839ead52 --- /dev/null +++ b/crates/jolt-dory-assist-verifier/tests/completeness.rs @@ -0,0 +1,13 @@ +#![expect( + dead_code, + reason = "Completeness oracle scaffolding shares registry metadata with focused test modules." +)] + +#[path = "completeness/mod.rs"] +mod completeness; +mod support; + +#[test] +fn completeness_case_registry_is_wired() { + completeness::assert_registry_is_wired(); +} diff --git a/crates/jolt-dory-assist-verifier/tests/completeness/cases.rs b/crates/jolt-dory-assist-verifier/tests/completeness/cases.rs new file mode 100644 index 0000000000..7693796440 --- /dev/null +++ b/crates/jolt-dory-assist-verifier/tests/completeness/cases.rs @@ -0,0 +1,31 @@ +use crate::support::{FixtureId, TestCase, VerifierPhase}; + +pub const CLEAR_BASE: TestCase = TestCase { + name: "clear_valid_assist_proof_accepts", + zk: false, + fixture: FixtureId::ClearBase, + checked_at: VerifierPhase::NativeOutput, +}; + +pub const ZK_BASE: TestCase = TestCase { + name: "zk_valid_assist_proof_accepts", + zk: true, + fixture: FixtureId::ZkBase, + checked_at: VerifierPhase::NativeOutput, +}; + +pub const CLEAR_MULTIROUND: TestCase = TestCase { + name: "clear_multiround_valid_assist_proof_accepts", + zk: false, + fixture: FixtureId::ClearMultiround, + checked_at: VerifierPhase::NativeOutput, +}; + +pub const ZK_MULTIROUND: TestCase = TestCase { + name: "zk_multiround_valid_assist_proof_accepts", + zk: true, + fixture: FixtureId::ZkMultiround, + checked_at: VerifierPhase::NativeOutput, +}; + +pub const ALL: &[TestCase] = &[CLEAR_BASE, ZK_BASE, CLEAR_MULTIROUND, ZK_MULTIROUND]; diff --git a/crates/jolt-dory-assist-verifier/tests/completeness/fixtures.rs b/crates/jolt-dory-assist-verifier/tests/completeness/fixtures.rs new file mode 100644 index 0000000000..04fd7f7960 --- /dev/null +++ b/crates/jolt-dory-assist-verifier/tests/completeness/fixtures.rs @@ -0,0 +1,41 @@ +use crate::support::{FixtureId, FixtureMetadata}; + +pub fn metadata(id: FixtureId) -> FixtureMetadata { + match id { + FixtureId::ClearBase => FixtureMetadata { + id, + name: "clear base Dory-assist fixture", + zk: false, + expected_accepts: true, + notes: "Canonical clear-mode fixture used as the prover/verifier completeness oracle.", + }, + FixtureId::ClearMultiround => FixtureMetadata { + id, + name: "clear multi-round Dory-assist fixture", + zk: false, + expected_accepts: true, + notes: "Clear-mode fixture with a two-round Dory-reduce proof used to pin multi-round verifier completeness.", + }, + FixtureId::ZkBase => FixtureMetadata { + id, + name: "ZK base Dory-assist fixture", + zk: true, + expected_accepts: true, + notes: "Canonical ZK-mode fixture used as the prover/verifier completeness oracle.", + }, + FixtureId::ZkMultiround => FixtureMetadata { + id, + name: "ZK multi-round Dory-assist fixture", + zk: true, + expected_accepts: true, + notes: "ZK-mode fixture with a two-round Dory-reduce proof used to pin multi-round verifier completeness.", + }, + _ => FixtureMetadata { + id, + name: "soundness-only fixture", + zk: matches!(id, FixtureId::ZkInputMismatch), + expected_accepts: false, + notes: "Reserved for Dory-assist soundness tests.", + }, + } +} diff --git a/crates/jolt-dory-assist-verifier/tests/completeness/mod.rs b/crates/jolt-dory-assist-verifier/tests/completeness/mod.rs new file mode 100644 index 0000000000..00b20ce8a8 --- /dev/null +++ b/crates/jolt-dory-assist-verifier/tests/completeness/mod.rs @@ -0,0 +1,12 @@ +pub mod cases; +pub mod fixtures; +pub mod oracle; + +use crate::support::{assert_case_metadata_matches, assert_unique_case_names}; + +pub fn assert_registry_is_wired() { + assert_unique_case_names(cases::ALL); + for case in cases::ALL { + assert_case_metadata_matches(*case, fixtures::metadata(case.fixture)); + } +} diff --git a/crates/jolt-dory-assist-verifier/tests/completeness/oracle.rs b/crates/jolt-dory-assist-verifier/tests/completeness/oracle.rs new file mode 100644 index 0000000000..4baf7a4c0e --- /dev/null +++ b/crates/jolt-dory-assist-verifier/tests/completeness/oracle.rs @@ -0,0 +1,23 @@ +use crate::support::{ + assert_accepts, clear_base_case, clear_multiround_case, zk_base_case, zk_multiround_case, +}; + +#[test] +fn clear_valid_assist_proof_accepts() { + assert_accepts(clear_base_case().verify_clear()); +} + +#[test] +fn clear_multiround_valid_assist_proof_accepts() { + assert_accepts(clear_multiround_case().verify_clear()); +} + +#[test] +fn zk_valid_assist_proof_accepts() { + assert_accepts(zk_base_case().verify_zk()); +} + +#[test] +fn zk_multiround_valid_assist_proof_accepts() { + assert_accepts(zk_multiround_case().verify_zk()); +} diff --git a/crates/jolt-dory-assist-verifier/tests/generic_boundary.rs b/crates/jolt-dory-assist-verifier/tests/generic_boundary.rs new file mode 100644 index 0000000000..c25e8f0762 --- /dev/null +++ b/crates/jolt-dory-assist-verifier/tests/generic_boundary.rs @@ -0,0 +1,129 @@ +#![expect( + dead_code, + reason = "The shared fixture support exposes helpers for the broader verifier harness." +)] + +mod support; + +use support::{ + assert_accepts, assert_rejects, clear_base_case, clear_base_case_with_transcript, + clear_multiround_case, clear_multiround_case_with_transcript, tamper_clear_eval, + tamper_public_output, zk_base_case, zk_base_case_with_transcript, zk_multiround_case, + zk_multiround_case_with_transcript, +}; + +use jolt_field::Fr; +use jolt_transcript::PoseidonTranscript; + +type PoseidonFrTranscript = PoseidonTranscript; + +#[test] +fn clear_valid_fixture_accepts_through_pcs_assist_trait() { + assert_accepts(clear_base_case().verify_clear_via_pcs_assist()); +} + +#[test] +fn clear_multiround_fixture_accepts_through_pcs_assist_trait() { + assert_accepts(clear_multiround_case().verify_clear_via_pcs_assist()); +} + +#[test] +fn zk_valid_fixture_accepts_through_pcs_assist_trait() { + let case = zk_base_case(); + let direct = case.verify_zk(); + let via_trait = case.verify_zk_via_pcs_assist(); + + assert!( + matches!((&direct, &via_trait), (Ok(direct), Ok(via_trait)) if direct == via_trait), + "direct ZK verify and PCS-assist trait verify diverged: direct={direct:?}, via_trait={via_trait:?}", + ); +} + +#[test] +fn zk_multiround_fixture_accepts_through_pcs_assist_trait() { + let case = zk_multiround_case(); + let direct = case.verify_zk(); + let via_trait = case.verify_zk_via_pcs_assist(); + + assert!( + matches!((&direct, &via_trait), (Ok(direct), Ok(via_trait)) if direct == via_trait), + "direct ZK multiround verify and PCS-assist trait verify diverged: direct={direct:?}, via_trait={via_trait:?}", + ); +} + +#[test] +fn clear_tampered_eval_rejects_through_pcs_assist_trait() { + let mut case = clear_base_case(); + tamper_clear_eval(&mut case); + + assert_rejects(case.verify_clear_via_pcs_assist()); +} + +#[test] +fn zk_tampered_public_output_rejects_through_pcs_assist_trait() { + let mut case = zk_base_case(); + tamper_public_output(&mut case); + + assert_rejects(case.verify_zk_via_pcs_assist()); +} + +#[test] +fn clear_poseidon_fixture_accepts_through_pcs_assist_trait() { + assert_accepts( + clear_base_case_with_transcript::() + .verify_clear_via_pcs_assist_with_transcript::(), + ); +} + +#[test] +fn clear_multiround_poseidon_fixture_accepts_through_pcs_assist_trait() { + assert_accepts( + clear_multiround_case_with_transcript::() + .verify_clear_via_pcs_assist_with_transcript::(), + ); +} + +#[test] +fn zk_poseidon_fixture_accepts_through_pcs_assist_trait() { + assert_accepts( + zk_base_case_with_transcript::() + .verify_zk_via_pcs_assist_with_transcript::(), + ); +} + +#[test] +fn zk_multiround_poseidon_fixture_accepts_through_pcs_assist_trait() { + assert_accepts( + zk_multiround_case_with_transcript::() + .verify_zk_via_pcs_assist_with_transcript::(), + ); +} + +#[test] +fn clear_blake_fixture_rejects_under_poseidon_transcript() { + assert_rejects( + clear_base_case().verify_clear_via_pcs_assist_with_transcript::(), + ); +} + +#[test] +fn clear_multiround_blake_fixture_rejects_under_poseidon_transcript() { + assert_rejects( + clear_multiround_case() + .verify_clear_via_pcs_assist_with_transcript::(), + ); +} + +#[test] +fn zk_blake_fixture_rejects_under_poseidon_transcript() { + assert_rejects( + zk_base_case().verify_zk_via_pcs_assist_with_transcript::(), + ); +} + +#[test] +fn zk_multiround_blake_fixture_rejects_under_poseidon_transcript() { + assert_rejects( + zk_multiround_case().verify_zk_via_pcs_assist_with_transcript::(), + ); +} diff --git a/crates/jolt-dory-assist-verifier/tests/soundness.rs b/crates/jolt-dory-assist-verifier/tests/soundness.rs new file mode 100644 index 0000000000..56c0443a45 --- /dev/null +++ b/crates/jolt-dory-assist-verifier/tests/soundness.rs @@ -0,0 +1,13 @@ +#![expect( + dead_code, + reason = "Soundness oracle scaffolding shares registry metadata with focused tamper modules." +)] + +#[path = "soundness/mod.rs"] +mod soundness; +mod support; + +#[test] +fn soundness_case_registry_is_wired() { + soundness::assert_registry_is_wired(); +} diff --git a/crates/jolt-dory-assist-verifier/tests/soundness/fixtures.rs b/crates/jolt-dory-assist-verifier/tests/soundness/fixtures.rs new file mode 100644 index 0000000000..c498208120 --- /dev/null +++ b/crates/jolt-dory-assist-verifier/tests/soundness/fixtures.rs @@ -0,0 +1,86 @@ +use crate::support::{FixtureId, FixtureMetadata}; + +pub fn metadata(id: FixtureId) -> FixtureMetadata { + match id { + FixtureId::ClearInputMismatch => FixtureMetadata { + id, + name: "clear opening input mismatch", + zk: false, + expected_accepts: false, + notes: "Tampered clear eval or opening point must be rejected.", + }, + FixtureId::ZkInputMismatch => FixtureMetadata { + id, + name: "ZK opening input mismatch", + zk: true, + expected_accepts: false, + notes: "Tampered ZK opening inputs, transcript scalars, and Dory proof artifacts must be rejected.", + }, + FixtureId::StagePayloadMismatch => FixtureMetadata { + id, + name: "stage payload mismatch", + zk: false, + expected_accepts: false, + notes: "Tampered stage payloads must fail before the final verifier result.", + }, + FixtureId::OpeningClaimMismatch => FixtureMetadata { + id, + name: "packed opening claim mismatch", + zk: false, + expected_accepts: false, + notes: "Tampered packed opening claims must be rejected by the Hyrax opening stage.", + }, + FixtureId::HyraxOpeningMismatch => FixtureMetadata { + id, + name: "Hyrax opening proof mismatch", + zk: false, + expected_accepts: false, + notes: "Tampered Hyrax opening proof payloads must be rejected.", + }, + FixtureId::DenseCommitmentMismatch => FixtureMetadata { + id, + name: "dense witness commitment mismatch", + zk: false, + expected_accepts: false, + notes: "Tampered packed witness commitments must be rejected.", + }, + FixtureId::PublicOutputMismatch => FixtureMetadata { + id, + name: "public output mismatch", + zk: false, + expected_accepts: false, + notes: "Tampered pre-final-exponentiation output must be rejected.", + }, + FixtureId::ZkPublicOutputMismatch => FixtureMetadata { + id, + name: "ZK public output mismatch", + zk: true, + expected_accepts: false, + notes: "Tampered ZK pre-final-exponentiation output must be rejected.", + }, + FixtureId::NativeFinalInputMismatch => FixtureMetadata { + id, + name: "native-final input mismatch", + zk: false, + expected_accepts: false, + notes: "Tampered native-final reducer-state input claim must be rejected.", + }, + FixtureId::ZkNativeFinalInputMismatch => FixtureMetadata { + id, + name: "ZK native-final input mismatch", + zk: true, + expected_accepts: false, + notes: "Tampered ZK native-final reducer-state input claim must be rejected.", + }, + FixtureId::ClearBase + | FixtureId::ClearMultiround + | FixtureId::ZkBase + | FixtureId::ZkMultiround => FixtureMetadata { + id, + name: "completeness-only fixture", + zk: matches!(id, FixtureId::ZkBase | FixtureId::ZkMultiround), + expected_accepts: true, + notes: "Reserved for Dory-assist completeness tests.", + }, + } +} diff --git a/crates/jolt-dory-assist-verifier/tests/soundness/mod.rs b/crates/jolt-dory-assist-verifier/tests/soundness/mod.rs new file mode 100644 index 0000000000..1185fd6ee9 --- /dev/null +++ b/crates/jolt-dory-assist-verifier/tests/soundness/mod.rs @@ -0,0 +1,11 @@ +pub mod fixtures; +pub mod tampering; + +use crate::support::{assert_case_metadata_matches, assert_unique_case_names}; + +pub fn assert_registry_is_wired() { + assert_unique_case_names(tampering::ALL); + for case in tampering::ALL { + assert_case_metadata_matches(*case, fixtures::metadata(case.fixture)); + } +} diff --git a/crates/jolt-dory-assist-verifier/tests/soundness/tampering/inputs.rs b/crates/jolt-dory-assist-verifier/tests/soundness/tampering/inputs.rs new file mode 100644 index 0000000000..de90de3a24 --- /dev/null +++ b/crates/jolt-dory-assist-verifier/tests/soundness/tampering/inputs.rs @@ -0,0 +1,186 @@ +use crate::support::{ + assert_rejects, clear_base_case, tamper_checked_input_digest, tamper_clear_eval, + tamper_dory_final_artifact, tamper_dory_proof_artifact, tamper_dory_reduce_dimensions, + tamper_dory_reduce_round_artifact, tamper_dory_scalar_product_artifact, + tamper_dory_vmv_c_artifact, tamper_dory_vmv_e1_artifact, tamper_dory_zk_artifact, + tamper_dory_zk_y_com_artifact, tamper_gt_dimensions, tamper_jolt_commitment_claim, + tamper_jolt_commitment_gt_claim, tamper_jolt_evaluation_claim, tamper_opening_point, + tamper_packing_dimensions, tamper_transcript_scalar_claim, tamper_verifier_setup_artifact, + tamper_verifier_setup_digest, tamper_zk_sigma_c_transcript_scalar_claim, zk_base_case, + zk_multiround_case, +}; + +#[test] +fn tampered_clear_opening_eval_rejects() { + let mut case = clear_base_case(); + tamper_clear_eval(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_clear_opening_point_rejects() { + let mut case = clear_base_case(); + tamper_opening_point(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_zk_opening_point_rejects() { + let mut case = zk_base_case(); + tamper_opening_point(&mut case); + assert_rejects(case.verify_zk()); +} + +#[test] +fn tampered_checked_input_digest_rejects() { + let mut case = clear_base_case(); + tamper_checked_input_digest(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_verifier_setup_digest_rejects() { + let mut case = clear_base_case(); + tamper_verifier_setup_digest(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_verifier_setup_artifact_rejects() { + let mut case = clear_base_case(); + tamper_verifier_setup_artifact(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_dory_proof_artifact_rejects() { + let mut case = clear_base_case(); + tamper_dory_proof_artifact(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_dory_vmv_c_artifact_rejects() { + let mut case = clear_base_case(); + tamper_dory_vmv_c_artifact(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_dory_vmv_e1_artifact_rejects() { + let mut case = clear_base_case(); + tamper_dory_vmv_e1_artifact(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_dory_zk_artifact_rejects() { + let mut case = clear_base_case(); + tamper_dory_zk_artifact(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_zk_multiround_dory_e2_artifact_rejects() { + let mut case = zk_multiround_case(); + tamper_dory_zk_artifact(&mut case); + assert_rejects(case.verify_zk()); +} + +#[test] +fn tampered_zk_multiround_dory_y_com_artifact_rejects() { + let mut case = zk_multiround_case(); + tamper_dory_zk_y_com_artifact(&mut case); + assert_rejects(case.verify_zk()); +} + +#[test] +fn tampered_zk_multiround_dory_scalar_product_artifact_rejects() { + let mut case = zk_multiround_case(); + tamper_dory_scalar_product_artifact(&mut case); + assert_rejects(case.verify_zk()); +} + +#[test] +fn tampered_dory_reduce_round_artifact_rejects() { + let mut case = clear_base_case(); + tamper_dory_reduce_round_artifact(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_zk_multiround_dory_reduce_round_artifact_rejects() { + let mut case = zk_multiround_case(); + tamper_dory_reduce_round_artifact(&mut case); + assert_rejects(case.verify_zk()); +} + +#[test] +fn tampered_dory_reduce_dimensions_rejects() { + let mut case = clear_base_case(); + tamper_dory_reduce_dimensions(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_zk_multiround_dory_reduce_dimensions_rejects() { + let mut case = zk_multiround_case(); + tamper_dory_reduce_dimensions(&mut case); + assert_rejects(case.verify_zk()); +} + +#[test] +fn tampered_gt_dimensions_rejects() { + let mut case = clear_base_case(); + tamper_gt_dimensions(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_packing_dimensions_rejects() { + let mut case = clear_base_case(); + tamper_packing_dimensions(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_dory_final_artifact_rejects() { + let mut case = clear_base_case(); + tamper_dory_final_artifact(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_jolt_commitment_claim_rejects() { + let mut case = clear_base_case(); + tamper_jolt_commitment_claim(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_jolt_commitment_gt_claim_rejects() { + let mut case = clear_base_case(); + tamper_jolt_commitment_gt_claim(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_jolt_evaluation_claim_rejects() { + let mut case = clear_base_case(); + tamper_jolt_evaluation_claim(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_transcript_scalar_claim_rejects() { + let mut case = clear_base_case(); + tamper_transcript_scalar_claim(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_zk_multiround_sigma_c_transcript_scalar_claim_rejects() { + let mut case = zk_multiround_case(); + tamper_zk_sigma_c_transcript_scalar_claim(&mut case); + assert_rejects(case.verify_zk()); +} diff --git a/crates/jolt-dory-assist-verifier/tests/soundness/tampering/manifest.rs b/crates/jolt-dory-assist-verifier/tests/soundness/tampering/manifest.rs new file mode 100644 index 0000000000..c4630df37e --- /dev/null +++ b/crates/jolt-dory-assist-verifier/tests/soundness/tampering/manifest.rs @@ -0,0 +1,562 @@ +use crate::{ + soundness::tampering, + support::{assert_unique_tamper_target_names, TamperTarget}, +}; + +pub const TARGETS: &[TamperTarget] = &[ + TamperTarget { + name: tampering::CLEAR_INPUT_EVAL.name, + fixture: tampering::CLEAR_INPUT_EVAL.fixture, + checked_at: tampering::CLEAR_INPUT_EVAL.checked_at, + coverage_note: "active: checked by the continued transcript digest bound into Stage 1 public claims", + }, + TamperTarget { + name: tampering::CLEAR_INPUT_POINT.name, + fixture: tampering::CLEAR_INPUT_POINT.fixture, + checked_at: tampering::CLEAR_INPUT_POINT.checked_at, + coverage_note: "active: checked by the continued transcript digest bound into Stage 1 public claims", + }, + TamperTarget { + name: tampering::ZK_INPUT_POINT.name, + fixture: tampering::ZK_INPUT_POINT.fixture, + checked_at: tampering::ZK_INPUT_POINT.checked_at, + coverage_note: "active: checked by the continued transcript digest bound into Stage 1 public claims", + }, + TamperTarget { + name: tampering::CHECKED_INPUT_DIGEST.name, + fixture: tampering::CHECKED_INPUT_DIGEST.fixture, + checked_at: tampering::CHECKED_INPUT_DIGEST.checked_at, + coverage_note: "active: checked by the continued transcript digest bound into Stage 1 public claims", + }, + TamperTarget { + name: tampering::VERIFIER_SETUP_DIGEST.name, + fixture: tampering::VERIFIER_SETUP_DIGEST.fixture, + checked_at: tampering::VERIFIER_SETUP_DIGEST.checked_at, + coverage_note: "active: checked by the verifier setup digest public claim", + }, + TamperTarget { + name: tampering::VERIFIER_SETUP_ARTIFACT.name, + fixture: tampering::VERIFIER_SETUP_ARTIFACT.fixture, + checked_at: tampering::VERIFIER_SETUP_ARTIFACT.checked_at, + coverage_note: "active: checked by concrete verifier setup artifact public claims", + }, + TamperTarget { + name: tampering::DORY_PROOF_ARTIFACT.name, + fixture: tampering::DORY_PROOF_ARTIFACT.fixture, + checked_at: tampering::DORY_PROOF_ARTIFACT.checked_at, + coverage_note: "active: checked by the Dory proof artifact digest public claim", + }, + TamperTarget { + name: tampering::DORY_VMV_C_ARTIFACT.name, + fixture: tampering::DORY_VMV_C_ARTIFACT.fixture, + checked_at: tampering::DORY_VMV_C_ARTIFACT.checked_at, + coverage_note: "active: checked by the concrete Dory VMV C GT artifact public claim", + }, + TamperTarget { + name: tampering::DORY_VMV_E1_ARTIFACT.name, + fixture: tampering::DORY_VMV_E1_ARTIFACT.fixture, + checked_at: tampering::DORY_VMV_E1_ARTIFACT.checked_at, + coverage_note: "active: checked by the concrete Dory VMV E1 G1 artifact public claim", + }, + TamperTarget { + name: tampering::DORY_ZK_ARTIFACT.name, + fixture: tampering::DORY_ZK_ARTIFACT.fixture, + checked_at: tampering::DORY_ZK_ARTIFACT.checked_at, + coverage_note: "active: checked by the concrete Dory ZK artifact public claim", + }, + TamperTarget { + name: tampering::ZK_MULTIROUND_DORY_E2_ARTIFACT.name, + fixture: tampering::ZK_MULTIROUND_DORY_E2_ARTIFACT.fixture, + checked_at: tampering::ZK_MULTIROUND_DORY_E2_ARTIFACT.checked_at, + coverage_note: "active: checked by the concrete ZK Dory e2 artifact public claim on the multiround verifier path", + }, + TamperTarget { + name: tampering::ZK_MULTIROUND_DORY_Y_COM_ARTIFACT.name, + fixture: tampering::ZK_MULTIROUND_DORY_Y_COM_ARTIFACT.fixture, + checked_at: tampering::ZK_MULTIROUND_DORY_Y_COM_ARTIFACT.checked_at, + coverage_note: "active: checked by the concrete ZK Dory y_com artifact public claim on the multiround verifier path", + }, + TamperTarget { + name: tampering::ZK_MULTIROUND_DORY_SCALAR_PRODUCT_ARTIFACT.name, + fixture: tampering::ZK_MULTIROUND_DORY_SCALAR_PRODUCT_ARTIFACT.fixture, + checked_at: tampering::ZK_MULTIROUND_DORY_SCALAR_PRODUCT_ARTIFACT.checked_at, + coverage_note: "active: checked by the concrete ZK scalar-product artifact public claim on the multiround verifier path", + }, + TamperTarget { + name: tampering::DORY_REDUCE_ROUND_ARTIFACT.name, + fixture: tampering::DORY_REDUCE_ROUND_ARTIFACT.fixture, + checked_at: tampering::DORY_REDUCE_ROUND_ARTIFACT.checked_at, + coverage_note: "active: checked by concrete Dory reduce-round artifact public claims", + }, + TamperTarget { + name: tampering::ZK_MULTIROUND_DORY_REDUCE_ROUND_ARTIFACT.name, + fixture: tampering::ZK_MULTIROUND_DORY_REDUCE_ROUND_ARTIFACT.fixture, + checked_at: tampering::ZK_MULTIROUND_DORY_REDUCE_ROUND_ARTIFACT.checked_at, + coverage_note: "active: checked by concrete Dory reduce-round artifact public claims on the ZK multiround verifier path", + }, + TamperTarget { + name: tampering::DORY_REDUCE_DIMENSIONS.name, + fixture: tampering::DORY_REDUCE_DIMENSIONS.fixture, + checked_at: tampering::DORY_REDUCE_DIMENSIONS.checked_at, + coverage_note: "active: checked by binding proof Dory-reduce dimensions to the PCS proof point length and reduce-round count", + }, + TamperTarget { + name: tampering::ZK_MULTIROUND_DORY_REDUCE_DIMENSIONS.name, + fixture: tampering::ZK_MULTIROUND_DORY_REDUCE_DIMENSIONS.fixture, + checked_at: tampering::ZK_MULTIROUND_DORY_REDUCE_DIMENSIONS.checked_at, + coverage_note: "active: checked by binding ZK multiround proof Dory-reduce dimensions to the PCS proof point length and reduce-round count", + }, + TamperTarget { + name: tampering::GT_DIMENSIONS.name, + fixture: tampering::GT_DIMENSIONS.fixture, + checked_at: tampering::GT_DIMENSIONS.checked_at, + coverage_note: "active: checked by rejecting non-canonical active protocol dimensions before deriving stage catalogs", + }, + TamperTarget { + name: tampering::PACKING_DIMENSIONS.name, + fixture: tampering::PACKING_DIMENSIONS.fixture, + checked_at: tampering::PACKING_DIMENSIONS.checked_at, + coverage_note: "active: checked by requiring prefix-packing dimensions to equal catalog-derived minimal dimensions", + }, + TamperTarget { + name: tampering::DORY_FINAL_ARTIFACT.name, + fixture: tampering::DORY_FINAL_ARTIFACT.fixture, + checked_at: tampering::DORY_FINAL_ARTIFACT.checked_at, + coverage_note: "active: checked by concrete Dory final scalar-product artifact public claims", + }, + TamperTarget { + name: tampering::JOLT_COMMITMENT_CLAIM.name, + fixture: tampering::JOLT_COMMITMENT_CLAIM.fixture, + checked_at: tampering::JOLT_COMMITMENT_CLAIM.checked_at, + coverage_note: "active: checked by the Jolt commitment digest public claim", + }, + TamperTarget { + name: tampering::JOLT_COMMITMENT_GT_CLAIM.name, + fixture: tampering::JOLT_COMMITMENT_GT_CLAIM.fixture, + checked_at: tampering::JOLT_COMMITMENT_GT_CLAIM.checked_at, + coverage_note: "active: checked by the concrete joint commitment GT artifact public claim", + }, + TamperTarget { + name: tampering::JOLT_EVALUATION_CLAIM.name, + fixture: tampering::JOLT_EVALUATION_CLAIM.fixture, + checked_at: tampering::JOLT_EVALUATION_CLAIM.checked_at, + coverage_note: "active: checked by the clear Jolt evaluation public claim", + }, + TamperTarget { + name: tampering::TRANSCRIPT_SCALAR_CLAIM.name, + fixture: tampering::TRANSCRIPT_SCALAR_CLAIM.fixture, + checked_at: tampering::TRANSCRIPT_SCALAR_CLAIM.checked_at, + coverage_note: "active: checked by the Fr-to-Fq transcript scalar public claim", + }, + TamperTarget { + name: tampering::ZK_MULTIROUND_SIGMA_C_TRANSCRIPT_SCALAR_CLAIM.name, + fixture: tampering::ZK_MULTIROUND_SIGMA_C_TRANSCRIPT_SCALAR_CLAIM.fixture, + checked_at: tampering::ZK_MULTIROUND_SIGMA_C_TRANSCRIPT_SCALAR_CLAIM.checked_at, + coverage_note: "active: checked by the ZK scalar-product sigma_c transcript scalar public claim on the multiround verifier path", + }, + TamperTarget { + name: tampering::STAGE1_PAYLOAD.name, + fixture: tampering::STAGE1_PAYLOAD.fixture, + checked_at: tampering::STAGE1_PAYLOAD.checked_at, + coverage_note: "active: checked by stage 1 canonical relation catalog binding", + }, + TamperTarget { + name: tampering::STAGE1_SUMCHECK_ROUNDS.name, + fixture: tampering::STAGE1_SUMCHECK_ROUNDS.fixture, + checked_at: tampering::STAGE1_SUMCHECK_ROUNDS.checked_at, + coverage_note: "active: checked by stage 1 compressed sumcheck transcript verification", + }, + TamperTarget { + name: tampering::STAGE1_RELATION_OUTPUT.name, + fixture: tampering::STAGE1_RELATION_OUTPUT.fixture, + checked_at: tampering::STAGE1_RELATION_OUTPUT.checked_at, + coverage_note: "active: checked by evaluating the stage 1 jolt-claims output expression", + }, + TamperTarget { + name: tampering::STAGE1_DIGIT_SELECTOR_OUTPUT.name, + fixture: tampering::STAGE1_DIGIT_SELECTOR_OUTPUT.fixture, + checked_at: tampering::STAGE1_DIGIT_SELECTOR_OUTPUT.checked_at, + coverage_note: "active: checked by the GT base-4 digit-selector relation", + }, + TamperTarget { + name: tampering::STAGE1_SHIFT_OUTPUT.name, + fixture: tampering::STAGE1_SHIFT_OUTPUT.fixture, + checked_at: tampering::STAGE1_SHIFT_OUTPUT.checked_at, + coverage_note: "active: checked by the GT exponentiation shift relation", + }, + TamperTarget { + name: tampering::STAGE1_SHIFT_PUBLIC.name, + fixture: tampering::STAGE1_SHIFT_PUBLIC.fixture, + checked_at: tampering::STAGE1_SHIFT_PUBLIC.checked_at, + coverage_note: "active: checked by the typed GT shift-kernel public claim on a nonzero shift accumulator", + }, + TamperTarget { + name: tampering::STAGE1_BOUNDARY_OUTPUT.name, + fixture: tampering::STAGE1_BOUNDARY_OUTPUT.fixture, + checked_at: tampering::STAGE1_BOUNDARY_OUTPUT.checked_at, + coverage_note: "active: checked by the GT exponentiation boundary relation", + }, + TamperTarget { + name: tampering::STAGE1_BOUNDARY_PUBLIC.name, + fixture: tampering::STAGE1_BOUNDARY_PUBLIC.fixture, + checked_at: tampering::STAGE1_BOUNDARY_PUBLIC.checked_at, + coverage_note: "active: checked by the typed GT boundary public claim", + }, + TamperTarget { + name: tampering::STAGE1_MULTIPLICATION_OUTPUT.name, + fixture: tampering::STAGE1_MULTIPLICATION_OUTPUT.fixture, + checked_at: tampering::STAGE1_MULTIPLICATION_OUTPUT.checked_at, + coverage_note: "active: checked by the GT multiplication quotient relation", + }, + TamperTarget { + name: tampering::STAGE1_G1_SCALAR_MULTIPLICATION_OUTPUT.name, + fixture: tampering::STAGE1_G1_SCALAR_MULTIPLICATION_OUTPUT.fixture, + checked_at: tampering::STAGE1_G1_SCALAR_MULTIPLICATION_OUTPUT.checked_at, + coverage_note: "active: checked by the G1 scalar-multiplication relation", + }, + TamperTarget { + name: tampering::STAGE1_G1_SHIFT_OUTPUT.name, + fixture: tampering::STAGE1_G1_SHIFT_OUTPUT.fixture, + checked_at: tampering::STAGE1_G1_SHIFT_OUTPUT.checked_at, + coverage_note: "active: checked by the G1 scalar-multiplication shift relation", + }, + TamperTarget { + name: tampering::STAGE1_G1_BOUNDARY_PUBLIC.name, + fixture: tampering::STAGE1_G1_BOUNDARY_PUBLIC.fixture, + checked_at: tampering::STAGE1_G1_BOUNDARY_PUBLIC.checked_at, + coverage_note: "active: checked by the G1 scalar-multiplication boundary public claim", + }, + TamperTarget { + name: tampering::STAGE1_G1_ADDITION_OUTPUT.name, + fixture: tampering::STAGE1_G1_ADDITION_OUTPUT.fixture, + checked_at: tampering::STAGE1_G1_ADDITION_OUTPUT.checked_at, + coverage_note: "active: checked by the G1 addition relation", + }, + TamperTarget { + name: tampering::STAGE1_G2_SCALAR_MULTIPLICATION_OUTPUT.name, + fixture: tampering::STAGE1_G2_SCALAR_MULTIPLICATION_OUTPUT.fixture, + checked_at: tampering::STAGE1_G2_SCALAR_MULTIPLICATION_OUTPUT.checked_at, + coverage_note: "active: checked by the G2 scalar-multiplication relation", + }, + TamperTarget { + name: tampering::STAGE1_G2_SHIFT_OUTPUT.name, + fixture: tampering::STAGE1_G2_SHIFT_OUTPUT.fixture, + checked_at: tampering::STAGE1_G2_SHIFT_OUTPUT.checked_at, + coverage_note: "active: checked by the G2 scalar-multiplication shift relation", + }, + TamperTarget { + name: tampering::STAGE1_G2_BOUNDARY_PUBLIC.name, + fixture: tampering::STAGE1_G2_BOUNDARY_PUBLIC.fixture, + checked_at: tampering::STAGE1_G2_BOUNDARY_PUBLIC.checked_at, + coverage_note: "active: checked by the G2 scalar-multiplication boundary public claim", + }, + TamperTarget { + name: tampering::STAGE1_G2_ADDITION_OUTPUT.name, + fixture: tampering::STAGE1_G2_ADDITION_OUTPUT.fixture, + checked_at: tampering::STAGE1_G2_ADDITION_OUTPUT.checked_at, + coverage_note: "active: checked by the G2 addition relation", + }, + TamperTarget { + name: tampering::STAGE1_LINE_STEP_OUTPUT.name, + fixture: tampering::STAGE1_LINE_STEP_OUTPUT.fixture, + checked_at: tampering::STAGE1_LINE_STEP_OUTPUT.checked_at, + coverage_note: "active: checked by the Miller-loop G2 line-step relation", + }, + TamperTarget { + name: tampering::STAGE1_LINE_EVALUATION_OUTPUT.name, + fixture: tampering::STAGE1_LINE_EVALUATION_OUTPUT.fixture, + checked_at: tampering::STAGE1_LINE_EVALUATION_OUTPUT.checked_at, + coverage_note: "active: checked by the Miller-loop sparse line-evaluation relation", + }, + TamperTarget { + name: tampering::STAGE1_PAIR_PRODUCT_OUTPUT.name, + fixture: tampering::STAGE1_PAIR_PRODUCT_OUTPUT.fixture, + checked_at: tampering::STAGE1_PAIR_PRODUCT_OUTPUT.checked_at, + coverage_note: "active: checked by the Miller-loop pair-product relation", + }, + TamperTarget { + name: tampering::STAGE1_ACCUMULATOR_OUTPUT.name, + fixture: tampering::STAGE1_ACCUMULATOR_OUTPUT.fixture, + checked_at: tampering::STAGE1_ACCUMULATOR_OUTPUT.checked_at, + coverage_note: "active: checked by the Miller-loop accumulator relation", + }, + TamperTarget { + name: tampering::STAGE1_MILLER_BOUNDARY_OUTPUT.name, + fixture: tampering::STAGE1_MILLER_BOUNDARY_OUTPUT.fixture, + checked_at: tampering::STAGE1_MILLER_BOUNDARY_OUTPUT.checked_at, + coverage_note: "active: checked by the Miller-loop boundary relation", + }, + TamperTarget { + name: tampering::STAGE1_DORY_REDUCE_GT_TRANSITION_OUTPUT.name, + fixture: tampering::STAGE1_DORY_REDUCE_GT_TRANSITION_OUTPUT.fixture, + checked_at: tampering::STAGE1_DORY_REDUCE_GT_TRANSITION_OUTPUT.checked_at, + coverage_note: "active: checked by the Dory reduce GT transition relation", + }, + TamperTarget { + name: tampering::STAGE1_DORY_REDUCE_G1_TRANSITION_OUTPUT.name, + fixture: tampering::STAGE1_DORY_REDUCE_G1_TRANSITION_OUTPUT.fixture, + checked_at: tampering::STAGE1_DORY_REDUCE_G1_TRANSITION_OUTPUT.checked_at, + coverage_note: "active: checked by the Dory reduce G1 transition relation", + }, + TamperTarget { + name: tampering::STAGE1_DORY_REDUCE_G2_TRANSITION_OUTPUT.name, + fixture: tampering::STAGE1_DORY_REDUCE_G2_TRANSITION_OUTPUT.fixture, + checked_at: tampering::STAGE1_DORY_REDUCE_G2_TRANSITION_OUTPUT.checked_at, + coverage_note: "active: checked by the Dory reduce G2 transition relation", + }, + TamperTarget { + name: tampering::STAGE1_DORY_REDUCE_SCALAR_FOLD_OUTPUT.name, + fixture: tampering::STAGE1_DORY_REDUCE_SCALAR_FOLD_OUTPUT.fixture, + checked_at: tampering::STAGE1_DORY_REDUCE_SCALAR_FOLD_OUTPUT.checked_at, + coverage_note: "active: checked by the Dory reduce scalar accumulator-fold relation", + }, + TamperTarget { + name: tampering::STAGE1_DORY_REDUCE_STATE_CHAIN_OUTPUT.name, + fixture: tampering::STAGE1_DORY_REDUCE_STATE_CHAIN_OUTPUT.fixture, + checked_at: tampering::STAGE1_DORY_REDUCE_STATE_CHAIN_OUTPUT.checked_at, + coverage_note: "active: checked by the multi-round Dory reduce next-row/current-row state-chain relation", + }, + TamperTarget { + name: tampering::STAGE1_DORY_REDUCE_BOUNDARY_OUTPUT.name, + fixture: tampering::STAGE1_DORY_REDUCE_BOUNDARY_OUTPUT.fixture, + checked_at: tampering::STAGE1_DORY_REDUCE_BOUNDARY_OUTPUT.checked_at, + coverage_note: "active: checked by the multi-round Dory reduce initial/final boundary relation", + }, + TamperTarget { + name: tampering::ZK_STAGE1_DORY_REDUCE_STATE_CHAIN_OUTPUT.name, + fixture: tampering::ZK_STAGE1_DORY_REDUCE_STATE_CHAIN_OUTPUT.fixture, + checked_at: tampering::ZK_STAGE1_DORY_REDUCE_STATE_CHAIN_OUTPUT.checked_at, + coverage_note: "active: checks the ZK multi-round Dory reduce next-row/current-row state-chain relation", + }, + TamperTarget { + name: tampering::ZK_STAGE1_DORY_REDUCE_BOUNDARY_OUTPUT.name, + fixture: tampering::ZK_STAGE1_DORY_REDUCE_BOUNDARY_OUTPUT.fixture, + checked_at: tampering::ZK_STAGE1_DORY_REDUCE_BOUNDARY_OUTPUT.checked_at, + coverage_note: "active: checks the ZK multi-round Dory reduce initial/final boundary relation", + }, + TamperTarget { + name: tampering::STAGE2_PAYLOAD.name, + fixture: tampering::STAGE2_PAYLOAD.fixture, + checked_at: tampering::STAGE2_PAYLOAD.checked_at, + coverage_note: "active: checked by stage 2 canonical direct-copy catalog binding", + }, + TamperTarget { + name: tampering::STAGE2_COPY_VALUE.name, + fixture: tampering::STAGE2_COPY_VALUE.fixture, + checked_at: tampering::STAGE2_COPY_VALUE.checked_at, + coverage_note: "active: checked by stage 2 direct equality over resolved copy endpoints", + }, + TamperTarget { + name: tampering::STAGE2_PUBLIC_VMV_C_COPY_VALUE.name, + fixture: tampering::STAGE2_PUBLIC_VMV_C_COPY_VALUE.fixture, + checked_at: tampering::STAGE2_PUBLIC_VMV_C_COPY_VALUE.checked_at, + coverage_note: "active: checks Dory VMV C public artifact is the GT exponentiation accumulator consumed by Stage 2", + }, + TamperTarget { + name: tampering::STAGE2_PUBLIC_VMV_E1_COPY_VALUE.name, + fixture: tampering::STAGE2_PUBLIC_VMV_E1_COPY_VALUE.fixture, + checked_at: tampering::STAGE2_PUBLIC_VMV_E1_COPY_VALUE.checked_at, + coverage_note: "active: checks Dory VMV E1 public artifact is the Miller-loop G1 evaluation point consumed by Stage 2", + }, + TamperTarget { + name: tampering::STAGE2_LINE_COPY_VALUE.name, + fixture: tampering::STAGE2_LINE_COPY_VALUE.fixture, + checked_at: tampering::STAGE2_LINE_COPY_VALUE.checked_at, + coverage_note: "active: checked by stage 2 direct equality over Miller-loop line copy endpoints", + }, + TamperTarget { + name: tampering::STAGE2_PAIR_PRODUCT_COPY_VALUE.name, + fixture: tampering::STAGE2_PAIR_PRODUCT_COPY_VALUE.fixture, + checked_at: tampering::STAGE2_PAIR_PRODUCT_COPY_VALUE.checked_at, + coverage_note: "active: checked by stage 2 direct equality over Miller-loop pair-product copy endpoints", + }, + TamperTarget { + name: tampering::STAGE2_PAIR_PRODUCT_QUOTIENT_COPY_VALUE.name, + fixture: tampering::STAGE2_PAIR_PRODUCT_QUOTIENT_COPY_VALUE.fixture, + checked_at: tampering::STAGE2_PAIR_PRODUCT_QUOTIENT_COPY_VALUE.checked_at, + coverage_note: "active: checked by stage 2 direct equality over Miller-loop pair-product quotient copy endpoints", + }, + TamperTarget { + name: tampering::STAGE2_ACCUMULATOR_COPY_VALUE.name, + fixture: tampering::STAGE2_ACCUMULATOR_COPY_VALUE.fixture, + checked_at: tampering::STAGE2_ACCUMULATOR_COPY_VALUE.checked_at, + coverage_note: "active: checked by stage 2 direct equality over Miller-loop accumulator copy endpoints", + }, + TamperTarget { + name: tampering::STAGE2_ACCUMULATOR_QUOTIENT_COPY_VALUE.name, + fixture: tampering::STAGE2_ACCUMULATOR_QUOTIENT_COPY_VALUE.fixture, + checked_at: tampering::STAGE2_ACCUMULATOR_QUOTIENT_COPY_VALUE.checked_at, + coverage_note: "active: checked by stage 2 direct equality over Miller-loop accumulator quotient copy endpoints", + }, + TamperTarget { + name: tampering::STAGE2_BOUNDARY_COPY_VALUE.name, + fixture: tampering::STAGE2_BOUNDARY_COPY_VALUE.fixture, + checked_at: tampering::STAGE2_BOUNDARY_COPY_VALUE.checked_at, + coverage_note: "active: checked by stage 2 direct equality over Miller-loop boundary copy endpoints", + }, + TamperTarget { + name: tampering::STAGE2_G1_SHIFT_COPY_VALUE.name, + fixture: tampering::STAGE2_G1_SHIFT_COPY_VALUE.fixture, + checked_at: tampering::STAGE2_G1_SHIFT_COPY_VALUE.checked_at, + coverage_note: "active: checked by stage 2 direct equality over G1 scalar-mul shift endpoints", + }, + TamperTarget { + name: tampering::STAGE2_G1_BOUNDARY_COPY_VALUE.name, + fixture: tampering::STAGE2_G1_BOUNDARY_COPY_VALUE.fixture, + checked_at: tampering::STAGE2_G1_BOUNDARY_COPY_VALUE.checked_at, + coverage_note: "active: checked by stage 2 direct equality over G1 scalar-mul boundary endpoints", + }, + TamperTarget { + name: tampering::STAGE2_G2_SHIFT_COPY_VALUE.name, + fixture: tampering::STAGE2_G2_SHIFT_COPY_VALUE.fixture, + checked_at: tampering::STAGE2_G2_SHIFT_COPY_VALUE.checked_at, + coverage_note: "active: checked by stage 2 direct equality over G2 scalar-mul shift endpoints", + }, + TamperTarget { + name: tampering::STAGE2_G2_BOUNDARY_COPY_VALUE.name, + fixture: tampering::STAGE2_G2_BOUNDARY_COPY_VALUE.fixture, + checked_at: tampering::STAGE2_G2_BOUNDARY_COPY_VALUE.checked_at, + coverage_note: "active: checked by stage 2 direct equality over G2 scalar-mul boundary endpoints", + }, + TamperTarget { + name: tampering::STAGE2_DORY_REDUCE_SCALAR_FOLD_COPY_VALUE.name, + fixture: tampering::STAGE2_DORY_REDUCE_SCALAR_FOLD_COPY_VALUE.fixture, + checked_at: tampering::STAGE2_DORY_REDUCE_SCALAR_FOLD_COPY_VALUE.checked_at, + coverage_note: "active: checks Dory reduce scalar fold factors are copied from the verifier transcript scalars", + }, + TamperTarget { + name: tampering::STAGE2_DORY_REDUCE_INITIAL_STATE_COPY_VALUE.name, + fixture: tampering::STAGE2_DORY_REDUCE_INITIAL_STATE_COPY_VALUE.fixture, + checked_at: tampering::STAGE2_DORY_REDUCE_INITIAL_STATE_COPY_VALUE.checked_at, + coverage_note: "active: checks the Dory reduce initial state is copied from the public Dory proof, commitment, and scalar identity values before transition checks", + }, + TamperTarget { + name: tampering::STAGE2_DORY_REDUCE_PROOF_ARTIFACT_COPY_VALUE.name, + fixture: tampering::STAGE2_DORY_REDUCE_PROOF_ARTIFACT_COPY_VALUE.fixture, + checked_at: tampering::STAGE2_DORY_REDUCE_PROOF_ARTIFACT_COPY_VALUE.checked_at, + coverage_note: "active: checked by stage 2 direct equality from Dory reduce proof-message artifacts into transition openings", + }, + TamperTarget { + name: tampering::STAGE2_DORY_REDUCE_SETUP_ARTIFACT_COPY_VALUE.name, + fixture: tampering::STAGE2_DORY_REDUCE_SETUP_ARTIFACT_COPY_VALUE.fixture, + checked_at: tampering::STAGE2_DORY_REDUCE_SETUP_ARTIFACT_COPY_VALUE.checked_at, + coverage_note: "active: checked by stage 2 direct equality from verifier setup chi/delta artifacts into transition openings", + }, + TamperTarget { + name: tampering::STAGE2_DORY_REDUCE_TRANSCRIPT_SCALAR_COPY_VALUE.name, + fixture: tampering::STAGE2_DORY_REDUCE_TRANSCRIPT_SCALAR_COPY_VALUE.fixture, + checked_at: tampering::STAGE2_DORY_REDUCE_TRANSCRIPT_SCALAR_COPY_VALUE.checked_at, + coverage_note: "active: checked by stage 2 direct equality from Fr-derived Dory reduce transcript scalars into transition openings", + }, + TamperTarget { + name: tampering::STAGE2_DORY_REDUCE_PUBLIC_FOLD_VALUE.name, + fixture: tampering::STAGE2_DORY_REDUCE_PUBLIC_FOLD_VALUE.fixture, + checked_at: tampering::STAGE2_DORY_REDUCE_PUBLIC_FOLD_VALUE.checked_at, + coverage_note: "active: checked by stage 2 Dory-reduce public folds for multi-round public vectors", + }, + TamperTarget { + name: tampering::ZK_STAGE2_DORY_REDUCE_PUBLIC_FOLD_VALUE.name, + fixture: tampering::ZK_STAGE2_DORY_REDUCE_PUBLIC_FOLD_VALUE.fixture, + checked_at: tampering::ZK_STAGE2_DORY_REDUCE_PUBLIC_FOLD_VALUE.checked_at, + coverage_note: "active: checks ZK stage 2 Dory-reduce public folds for multi-round public vectors with ZK transcript staging", + }, + TamperTarget { + name: tampering::STAGE3_PAYLOAD.name, + fixture: tampering::STAGE3_PAYLOAD.fixture, + checked_at: tampering::STAGE3_PAYLOAD.checked_at, + coverage_note: "active: checked by stage 3 prefix-weighted packed-eval binding", + }, + TamperTarget { + name: tampering::STAGE3_REDUCED_OPENINGS.name, + fixture: tampering::STAGE3_REDUCED_OPENINGS.fixture, + checked_at: tampering::STAGE3_REDUCED_OPENINGS.checked_at, + coverage_note: "active: checked by stage 3 canonical reduced-opening order binding", + }, + TamperTarget { + name: tampering::OPENING_CLAIM_POINT.name, + fixture: tampering::OPENING_CLAIM_POINT.fixture, + checked_at: tampering::OPENING_CLAIM_POINT.checked_at, + coverage_note: "active: checked by packed Hyrax opening verification", + }, + TamperTarget { + name: tampering::OPENING_CLAIM_EVAL.name, + fixture: tampering::OPENING_CLAIM_EVAL.fixture, + checked_at: tampering::OPENING_CLAIM_EVAL.checked_at, + coverage_note: "active: checked by packed-eval binding", + }, + TamperTarget { + name: tampering::HYRAX_OPENING_ROW.name, + fixture: tampering::HYRAX_OPENING_ROW.fixture, + checked_at: tampering::HYRAX_OPENING_ROW.checked_at, + coverage_note: "active: checked by Hyrax opening proof verification", + }, + TamperTarget { + name: tampering::HYRAX_OPENING_SCALAR.name, + fixture: tampering::HYRAX_OPENING_SCALAR.fixture, + checked_at: tampering::HYRAX_OPENING_SCALAR.checked_at, + coverage_note: "active: checked by Hyrax opening proof verification", + }, + TamperTarget { + name: tampering::DENSE_COMMITMENT.name, + fixture: tampering::DENSE_COMMITMENT.fixture, + checked_at: tampering::DENSE_COMMITMENT.checked_at, + coverage_note: "active: checked by packed witness commitment binding", + }, + TamperTarget { + name: tampering::PUBLIC_OUTPUT.name, + fixture: tampering::PUBLIC_OUTPUT.fixture, + checked_at: tampering::PUBLIC_OUTPUT.checked_at, + coverage_note: "active: checked by native pre-final-exponentiation output binding", + }, + TamperTarget { + name: tampering::ZK_PUBLIC_OUTPUT.name, + fixture: tampering::ZK_PUBLIC_OUTPUT.fixture, + checked_at: tampering::ZK_PUBLIC_OUTPUT.checked_at, + coverage_note: "active: checked by ZK native pre-final-exponentiation output binding", + }, + TamperTarget { + name: tampering::NATIVE_FINAL_INPUT.name, + fixture: tampering::NATIVE_FINAL_INPUT.fixture, + checked_at: tampering::NATIVE_FINAL_INPUT.checked_at, + coverage_note: "active: checked by native-final reducer-state public input binding", + }, + TamperTarget { + name: tampering::ZK_NATIVE_FINAL_INPUT.name, + fixture: tampering::ZK_NATIVE_FINAL_INPUT.fixture, + checked_at: tampering::ZK_NATIVE_FINAL_INPUT.checked_at, + coverage_note: "active: checked by ZK native-final reducer-state public input binding", + }, +]; + +#[test] +fn tamper_manifest_target_names_are_unique() { + assert_unique_tamper_target_names(TARGETS); +} + +#[test] +fn tamper_manifest_covers_registered_cases() { + let missing: Vec<_> = tampering::ALL + .iter() + .filter(|case| !TARGETS.iter().any(|target| target.name == case.name)) + .map(|case| case.name) + .collect(); + + assert!( + missing.is_empty(), + "tamper cases missing from manifest: {missing:?}", + ); +} + +#[test] +fn tamper_targets_are_documented() { + let undocumented: Vec<_> = TARGETS + .iter() + .filter(|target| target.coverage_note.is_empty()) + .map(|target| target.name) + .collect(); + + assert!( + undocumented.is_empty(), + "tamper targets need a coverage note: {undocumented:?}", + ); +} diff --git a/crates/jolt-dory-assist-verifier/tests/soundness/tampering/mod.rs b/crates/jolt-dory-assist-verifier/tests/soundness/tampering/mod.rs new file mode 100644 index 0000000000..4d11536e0c --- /dev/null +++ b/crates/jolt-dory-assist-verifier/tests/soundness/tampering/mod.rs @@ -0,0 +1,706 @@ +pub mod inputs; +pub mod manifest; +pub mod openings; +pub mod public_outputs; +pub mod stages; + +use crate::support::{FixtureId, TestCase, VerifierPhase}; + +pub const CLEAR_INPUT_EVAL: TestCase = TestCase { + name: "tamper_clear_opening_eval", + zk: false, + fixture: FixtureId::ClearInputMismatch, + checked_at: VerifierPhase::CheckedInputs, +}; + +pub const CLEAR_INPUT_POINT: TestCase = TestCase { + name: "tamper_clear_opening_point", + zk: false, + fixture: FixtureId::ClearInputMismatch, + checked_at: VerifierPhase::CheckedInputs, +}; + +pub const ZK_INPUT_POINT: TestCase = TestCase { + name: "tamper_zk_opening_point", + zk: true, + fixture: FixtureId::ZkInputMismatch, + checked_at: VerifierPhase::CheckedInputs, +}; + +pub const CHECKED_INPUT_DIGEST: TestCase = TestCase { + name: "tamper_checked_input_digest", + zk: false, + fixture: FixtureId::ClearInputMismatch, + checked_at: VerifierPhase::CheckedInputs, +}; + +pub const VERIFIER_SETUP_DIGEST: TestCase = TestCase { + name: "tamper_verifier_setup_digest", + zk: false, + fixture: FixtureId::ClearInputMismatch, + checked_at: VerifierPhase::CheckedInputs, +}; + +pub const VERIFIER_SETUP_ARTIFACT: TestCase = TestCase { + name: "tamper_verifier_setup_artifact", + zk: false, + fixture: FixtureId::ClearInputMismatch, + checked_at: VerifierPhase::CheckedInputs, +}; + +pub const DORY_PROOF_ARTIFACT: TestCase = TestCase { + name: "tamper_dory_proof_artifact", + zk: false, + fixture: FixtureId::ClearInputMismatch, + checked_at: VerifierPhase::CheckedInputs, +}; + +pub const DORY_VMV_C_ARTIFACT: TestCase = TestCase { + name: "tamper_dory_vmv_c_artifact", + zk: false, + fixture: FixtureId::ClearInputMismatch, + checked_at: VerifierPhase::CheckedInputs, +}; + +pub const DORY_VMV_E1_ARTIFACT: TestCase = TestCase { + name: "tamper_dory_vmv_e1_artifact", + zk: false, + fixture: FixtureId::ClearInputMismatch, + checked_at: VerifierPhase::CheckedInputs, +}; + +pub const DORY_ZK_ARTIFACT: TestCase = TestCase { + name: "tamper_dory_zk_artifact", + zk: false, + fixture: FixtureId::ClearInputMismatch, + checked_at: VerifierPhase::CheckedInputs, +}; + +pub const ZK_MULTIROUND_DORY_E2_ARTIFACT: TestCase = TestCase { + name: "tamper_zk_multiround_dory_e2_artifact", + zk: true, + fixture: FixtureId::ZkInputMismatch, + checked_at: VerifierPhase::CheckedInputs, +}; + +pub const ZK_MULTIROUND_DORY_Y_COM_ARTIFACT: TestCase = TestCase { + name: "tamper_zk_multiround_dory_y_com_artifact", + zk: true, + fixture: FixtureId::ZkInputMismatch, + checked_at: VerifierPhase::CheckedInputs, +}; + +pub const ZK_MULTIROUND_DORY_SCALAR_PRODUCT_ARTIFACT: TestCase = TestCase { + name: "tamper_zk_multiround_dory_scalar_product_artifact", + zk: true, + fixture: FixtureId::ZkInputMismatch, + checked_at: VerifierPhase::CheckedInputs, +}; + +pub const DORY_REDUCE_ROUND_ARTIFACT: TestCase = TestCase { + name: "tamper_dory_reduce_round_artifact", + zk: false, + fixture: FixtureId::ClearInputMismatch, + checked_at: VerifierPhase::CheckedInputs, +}; + +pub const ZK_MULTIROUND_DORY_REDUCE_ROUND_ARTIFACT: TestCase = TestCase { + name: "tamper_zk_multiround_dory_reduce_round_artifact", + zk: true, + fixture: FixtureId::ZkInputMismatch, + checked_at: VerifierPhase::CheckedInputs, +}; + +pub const DORY_REDUCE_DIMENSIONS: TestCase = TestCase { + name: "tamper_dory_reduce_dimensions", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::CheckedInputs, +}; + +pub const ZK_MULTIROUND_DORY_REDUCE_DIMENSIONS: TestCase = TestCase { + name: "tamper_zk_multiround_dory_reduce_dimensions", + zk: true, + fixture: FixtureId::ZkInputMismatch, + checked_at: VerifierPhase::CheckedInputs, +}; + +pub const GT_DIMENSIONS: TestCase = TestCase { + name: "tamper_gt_dimensions", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::CheckedInputs, +}; + +pub const PACKING_DIMENSIONS: TestCase = TestCase { + name: "tamper_packing_dimensions", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::CheckedInputs, +}; + +pub const DORY_FINAL_ARTIFACT: TestCase = TestCase { + name: "tamper_dory_final_artifact", + zk: false, + fixture: FixtureId::ClearInputMismatch, + checked_at: VerifierPhase::CheckedInputs, +}; + +pub const JOLT_COMMITMENT_CLAIM: TestCase = TestCase { + name: "tamper_jolt_commitment_claim", + zk: false, + fixture: FixtureId::ClearInputMismatch, + checked_at: VerifierPhase::CheckedInputs, +}; + +pub const JOLT_COMMITMENT_GT_CLAIM: TestCase = TestCase { + name: "tamper_jolt_commitment_gt_claim", + zk: false, + fixture: FixtureId::ClearInputMismatch, + checked_at: VerifierPhase::CheckedInputs, +}; + +pub const JOLT_EVALUATION_CLAIM: TestCase = TestCase { + name: "tamper_jolt_evaluation_claim", + zk: false, + fixture: FixtureId::ClearInputMismatch, + checked_at: VerifierPhase::CheckedInputs, +}; + +pub const TRANSCRIPT_SCALAR_CLAIM: TestCase = TestCase { + name: "tamper_transcript_scalar_claim", + zk: false, + fixture: FixtureId::ClearInputMismatch, + checked_at: VerifierPhase::CheckedInputs, +}; + +pub const ZK_MULTIROUND_SIGMA_C_TRANSCRIPT_SCALAR_CLAIM: TestCase = TestCase { + name: "tamper_zk_multiround_sigma_c_transcript_scalar_claim", + zk: true, + fixture: FixtureId::ZkInputMismatch, + checked_at: VerifierPhase::CheckedInputs, +}; + +pub const STAGE1_PAYLOAD: TestCase = TestCase { + name: "tamper_stage1_payload", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage1, +}; + +pub const STAGE1_SUMCHECK_ROUNDS: TestCase = TestCase { + name: "tamper_stage1_sumcheck_round_count", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage1, +}; + +pub const STAGE1_RELATION_OUTPUT: TestCase = TestCase { + name: "tamper_stage1_relation_output", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage1, +}; + +pub const STAGE1_DIGIT_SELECTOR_OUTPUT: TestCase = TestCase { + name: "tamper_stage1_digit_selector_output", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage1, +}; + +pub const STAGE1_SHIFT_OUTPUT: TestCase = TestCase { + name: "tamper_stage1_shift_output", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage1, +}; + +pub const STAGE1_SHIFT_PUBLIC: TestCase = TestCase { + name: "tamper_stage1_shift_public", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage1, +}; + +pub const STAGE1_BOUNDARY_OUTPUT: TestCase = TestCase { + name: "tamper_stage1_boundary_output", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage1, +}; + +pub const STAGE1_BOUNDARY_PUBLIC: TestCase = TestCase { + name: "tamper_stage1_boundary_public", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage1, +}; + +pub const STAGE1_MULTIPLICATION_OUTPUT: TestCase = TestCase { + name: "tamper_stage1_multiplication_output", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage1, +}; + +pub const STAGE1_G1_SCALAR_MULTIPLICATION_OUTPUT: TestCase = TestCase { + name: "tamper_stage1_g1_scalar_multiplication_output", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage1, +}; + +pub const STAGE1_G1_SHIFT_OUTPUT: TestCase = TestCase { + name: "tamper_stage1_g1_shift_output", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage1, +}; + +pub const STAGE1_G1_BOUNDARY_PUBLIC: TestCase = TestCase { + name: "tamper_stage1_g1_boundary_public", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage1, +}; + +pub const STAGE1_G1_ADDITION_OUTPUT: TestCase = TestCase { + name: "tamper_stage1_g1_addition_output", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage1, +}; + +pub const STAGE1_G2_SCALAR_MULTIPLICATION_OUTPUT: TestCase = TestCase { + name: "tamper_stage1_g2_scalar_multiplication_output", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage1, +}; + +pub const STAGE1_G2_SHIFT_OUTPUT: TestCase = TestCase { + name: "tamper_stage1_g2_shift_output", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage1, +}; + +pub const STAGE1_G2_BOUNDARY_PUBLIC: TestCase = TestCase { + name: "tamper_stage1_g2_boundary_public", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage1, +}; + +pub const STAGE1_G2_ADDITION_OUTPUT: TestCase = TestCase { + name: "tamper_stage1_g2_addition_output", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage1, +}; + +pub const STAGE1_LINE_STEP_OUTPUT: TestCase = TestCase { + name: "tamper_stage1_line_step_output", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage1, +}; + +pub const STAGE1_LINE_EVALUATION_OUTPUT: TestCase = TestCase { + name: "tamper_stage1_line_evaluation_output", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage1, +}; + +pub const STAGE1_PAIR_PRODUCT_OUTPUT: TestCase = TestCase { + name: "tamper_stage1_pair_product_output", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage1, +}; + +pub const STAGE1_ACCUMULATOR_OUTPUT: TestCase = TestCase { + name: "tamper_stage1_accumulator_output", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage1, +}; + +pub const STAGE1_MILLER_BOUNDARY_OUTPUT: TestCase = TestCase { + name: "tamper_stage1_miller_boundary_output", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage1, +}; + +pub const STAGE1_DORY_REDUCE_GT_TRANSITION_OUTPUT: TestCase = TestCase { + name: "tamper_stage1_dory_reduce_gt_transition_output", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage1, +}; + +pub const STAGE1_DORY_REDUCE_G1_TRANSITION_OUTPUT: TestCase = TestCase { + name: "tamper_stage1_dory_reduce_g1_transition_output", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage1, +}; + +pub const STAGE1_DORY_REDUCE_G2_TRANSITION_OUTPUT: TestCase = TestCase { + name: "tamper_stage1_dory_reduce_g2_transition_output", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage1, +}; + +pub const STAGE1_DORY_REDUCE_SCALAR_FOLD_OUTPUT: TestCase = TestCase { + name: "tamper_stage1_dory_reduce_scalar_fold_output", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage1, +}; + +pub const STAGE1_DORY_REDUCE_STATE_CHAIN_OUTPUT: TestCase = TestCase { + name: "tamper_stage1_dory_reduce_state_chain_output", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage1, +}; + +pub const STAGE1_DORY_REDUCE_BOUNDARY_OUTPUT: TestCase = TestCase { + name: "tamper_stage1_dory_reduce_boundary_output", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage1, +}; + +pub const ZK_STAGE1_DORY_REDUCE_STATE_CHAIN_OUTPUT: TestCase = TestCase { + name: "tamper_zk_stage1_dory_reduce_state_chain_output", + zk: true, + fixture: FixtureId::ZkMultiround, + checked_at: VerifierPhase::Stage1, +}; + +pub const ZK_STAGE1_DORY_REDUCE_BOUNDARY_OUTPUT: TestCase = TestCase { + name: "tamper_zk_stage1_dory_reduce_boundary_output", + zk: true, + fixture: FixtureId::ZkMultiround, + checked_at: VerifierPhase::Stage1, +}; + +pub const STAGE2_PAYLOAD: TestCase = TestCase { + name: "tamper_stage2_payload", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage2, +}; + +pub const STAGE2_COPY_VALUE: TestCase = TestCase { + name: "tamper_stage2_copy_value", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage2, +}; + +pub const STAGE2_PUBLIC_VMV_C_COPY_VALUE: TestCase = TestCase { + name: "tamper_stage2_public_vmv_c_copy_value", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage2, +}; + +pub const STAGE2_PUBLIC_VMV_E1_COPY_VALUE: TestCase = TestCase { + name: "tamper_stage2_public_vmv_e1_copy_value", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage2, +}; + +pub const STAGE2_LINE_COPY_VALUE: TestCase = TestCase { + name: "tamper_stage2_line_copy_value", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage2, +}; + +pub const STAGE2_PAIR_PRODUCT_COPY_VALUE: TestCase = TestCase { + name: "tamper_stage2_pair_product_copy_value", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage2, +}; + +pub const STAGE2_PAIR_PRODUCT_QUOTIENT_COPY_VALUE: TestCase = TestCase { + name: "tamper_stage2_pair_product_quotient_copy_value", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage2, +}; + +pub const STAGE2_ACCUMULATOR_COPY_VALUE: TestCase = TestCase { + name: "tamper_stage2_accumulator_copy_value", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage2, +}; + +pub const STAGE2_ACCUMULATOR_QUOTIENT_COPY_VALUE: TestCase = TestCase { + name: "tamper_stage2_accumulator_quotient_copy_value", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage2, +}; + +pub const STAGE2_BOUNDARY_COPY_VALUE: TestCase = TestCase { + name: "tamper_stage2_boundary_copy_value", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage2, +}; + +pub const STAGE2_G1_SHIFT_COPY_VALUE: TestCase = TestCase { + name: "tamper_stage2_g1_shift_copy_value", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage2, +}; + +pub const STAGE2_G1_BOUNDARY_COPY_VALUE: TestCase = TestCase { + name: "tamper_stage2_g1_boundary_copy_value", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage2, +}; + +pub const STAGE2_G2_SHIFT_COPY_VALUE: TestCase = TestCase { + name: "tamper_stage2_g2_shift_copy_value", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage2, +}; + +pub const STAGE2_G2_BOUNDARY_COPY_VALUE: TestCase = TestCase { + name: "tamper_stage2_g2_boundary_copy_value", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage2, +}; + +pub const STAGE2_DORY_REDUCE_SCALAR_FOLD_COPY_VALUE: TestCase = TestCase { + name: "tamper_stage2_dory_reduce_scalar_fold_copy_value", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage2, +}; + +pub const STAGE2_DORY_REDUCE_INITIAL_STATE_COPY_VALUE: TestCase = TestCase { + name: "tamper_stage2_dory_reduce_initial_state_copy_value", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage2, +}; + +pub const STAGE2_DORY_REDUCE_PROOF_ARTIFACT_COPY_VALUE: TestCase = TestCase { + name: "tamper_stage2_dory_reduce_proof_artifact_copy_value", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage2, +}; + +pub const STAGE2_DORY_REDUCE_SETUP_ARTIFACT_COPY_VALUE: TestCase = TestCase { + name: "tamper_stage2_dory_reduce_setup_artifact_copy_value", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage2, +}; + +pub const STAGE2_DORY_REDUCE_TRANSCRIPT_SCALAR_COPY_VALUE: TestCase = TestCase { + name: "tamper_stage2_dory_reduce_transcript_scalar_copy_value", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage2, +}; + +pub const STAGE2_DORY_REDUCE_PUBLIC_FOLD_VALUE: TestCase = TestCase { + name: "tamper_stage2_dory_reduce_public_fold_value", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage2, +}; + +pub const ZK_STAGE2_DORY_REDUCE_PUBLIC_FOLD_VALUE: TestCase = TestCase { + name: "tamper_zk_stage2_dory_reduce_public_fold_value", + zk: true, + fixture: FixtureId::ZkMultiround, + checked_at: VerifierPhase::Stage2, +}; + +pub const STAGE3_PAYLOAD: TestCase = TestCase { + name: "tamper_stage3_payload", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage3, +}; + +pub const STAGE3_REDUCED_OPENINGS: TestCase = TestCase { + name: "tamper_stage3_reduced_openings", + zk: false, + fixture: FixtureId::StagePayloadMismatch, + checked_at: VerifierPhase::Stage3, +}; + +pub const OPENING_CLAIM_POINT: TestCase = TestCase { + name: "tamper_opening_claim_point", + zk: false, + fixture: FixtureId::OpeningClaimMismatch, + checked_at: VerifierPhase::Opening, +}; + +pub const OPENING_CLAIM_EVAL: TestCase = TestCase { + name: "tamper_opening_claim_eval", + zk: false, + fixture: FixtureId::OpeningClaimMismatch, + checked_at: VerifierPhase::Opening, +}; + +pub const HYRAX_OPENING_ROW: TestCase = TestCase { + name: "tamper_hyrax_opening_row", + zk: false, + fixture: FixtureId::HyraxOpeningMismatch, + checked_at: VerifierPhase::Opening, +}; + +pub const HYRAX_OPENING_SCALAR: TestCase = TestCase { + name: "tamper_hyrax_opening_scalar", + zk: false, + fixture: FixtureId::HyraxOpeningMismatch, + checked_at: VerifierPhase::Opening, +}; + +pub const DENSE_COMMITMENT: TestCase = TestCase { + name: "tamper_dense_commitment", + zk: false, + fixture: FixtureId::DenseCommitmentMismatch, + checked_at: VerifierPhase::Opening, +}; + +pub const PUBLIC_OUTPUT: TestCase = TestCase { + name: "tamper_public_output", + zk: false, + fixture: FixtureId::PublicOutputMismatch, + checked_at: VerifierPhase::NativeOutput, +}; + +pub const ZK_PUBLIC_OUTPUT: TestCase = TestCase { + name: "tamper_zk_public_output", + zk: true, + fixture: FixtureId::ZkPublicOutputMismatch, + checked_at: VerifierPhase::NativeOutput, +}; + +pub const NATIVE_FINAL_INPUT: TestCase = TestCase { + name: "tamper_native_final_input", + zk: false, + fixture: FixtureId::NativeFinalInputMismatch, + checked_at: VerifierPhase::NativeOutput, +}; + +pub const ZK_NATIVE_FINAL_INPUT: TestCase = TestCase { + name: "tamper_zk_native_final_input", + zk: true, + fixture: FixtureId::ZkNativeFinalInputMismatch, + checked_at: VerifierPhase::NativeOutput, +}; + +pub const ALL: &[TestCase] = &[ + CLEAR_INPUT_EVAL, + CLEAR_INPUT_POINT, + ZK_INPUT_POINT, + CHECKED_INPUT_DIGEST, + VERIFIER_SETUP_DIGEST, + VERIFIER_SETUP_ARTIFACT, + DORY_PROOF_ARTIFACT, + DORY_VMV_C_ARTIFACT, + DORY_VMV_E1_ARTIFACT, + DORY_ZK_ARTIFACT, + ZK_MULTIROUND_DORY_E2_ARTIFACT, + ZK_MULTIROUND_DORY_Y_COM_ARTIFACT, + ZK_MULTIROUND_DORY_SCALAR_PRODUCT_ARTIFACT, + DORY_REDUCE_ROUND_ARTIFACT, + ZK_MULTIROUND_DORY_REDUCE_ROUND_ARTIFACT, + DORY_REDUCE_DIMENSIONS, + ZK_MULTIROUND_DORY_REDUCE_DIMENSIONS, + GT_DIMENSIONS, + PACKING_DIMENSIONS, + DORY_FINAL_ARTIFACT, + JOLT_COMMITMENT_CLAIM, + JOLT_COMMITMENT_GT_CLAIM, + JOLT_EVALUATION_CLAIM, + TRANSCRIPT_SCALAR_CLAIM, + ZK_MULTIROUND_SIGMA_C_TRANSCRIPT_SCALAR_CLAIM, + STAGE1_PAYLOAD, + STAGE1_SUMCHECK_ROUNDS, + STAGE1_RELATION_OUTPUT, + STAGE1_DIGIT_SELECTOR_OUTPUT, + STAGE1_SHIFT_OUTPUT, + STAGE1_SHIFT_PUBLIC, + STAGE1_BOUNDARY_OUTPUT, + STAGE1_BOUNDARY_PUBLIC, + STAGE1_MULTIPLICATION_OUTPUT, + STAGE1_G1_SCALAR_MULTIPLICATION_OUTPUT, + STAGE1_G1_SHIFT_OUTPUT, + STAGE1_G1_BOUNDARY_PUBLIC, + STAGE1_G1_ADDITION_OUTPUT, + STAGE1_G2_SCALAR_MULTIPLICATION_OUTPUT, + STAGE1_G2_SHIFT_OUTPUT, + STAGE1_G2_BOUNDARY_PUBLIC, + STAGE1_G2_ADDITION_OUTPUT, + STAGE1_LINE_STEP_OUTPUT, + STAGE1_LINE_EVALUATION_OUTPUT, + STAGE1_PAIR_PRODUCT_OUTPUT, + STAGE1_ACCUMULATOR_OUTPUT, + STAGE1_MILLER_BOUNDARY_OUTPUT, + STAGE1_DORY_REDUCE_GT_TRANSITION_OUTPUT, + STAGE1_DORY_REDUCE_G1_TRANSITION_OUTPUT, + STAGE1_DORY_REDUCE_G2_TRANSITION_OUTPUT, + STAGE1_DORY_REDUCE_SCALAR_FOLD_OUTPUT, + STAGE1_DORY_REDUCE_STATE_CHAIN_OUTPUT, + STAGE1_DORY_REDUCE_BOUNDARY_OUTPUT, + ZK_STAGE1_DORY_REDUCE_STATE_CHAIN_OUTPUT, + ZK_STAGE1_DORY_REDUCE_BOUNDARY_OUTPUT, + STAGE2_PAYLOAD, + STAGE2_COPY_VALUE, + STAGE2_PUBLIC_VMV_C_COPY_VALUE, + STAGE2_PUBLIC_VMV_E1_COPY_VALUE, + STAGE2_LINE_COPY_VALUE, + STAGE2_PAIR_PRODUCT_COPY_VALUE, + STAGE2_PAIR_PRODUCT_QUOTIENT_COPY_VALUE, + STAGE2_ACCUMULATOR_COPY_VALUE, + STAGE2_ACCUMULATOR_QUOTIENT_COPY_VALUE, + STAGE2_BOUNDARY_COPY_VALUE, + STAGE2_G1_SHIFT_COPY_VALUE, + STAGE2_G1_BOUNDARY_COPY_VALUE, + STAGE2_G2_SHIFT_COPY_VALUE, + STAGE2_G2_BOUNDARY_COPY_VALUE, + STAGE2_DORY_REDUCE_SCALAR_FOLD_COPY_VALUE, + STAGE2_DORY_REDUCE_INITIAL_STATE_COPY_VALUE, + STAGE2_DORY_REDUCE_PROOF_ARTIFACT_COPY_VALUE, + STAGE2_DORY_REDUCE_SETUP_ARTIFACT_COPY_VALUE, + STAGE2_DORY_REDUCE_TRANSCRIPT_SCALAR_COPY_VALUE, + STAGE2_DORY_REDUCE_PUBLIC_FOLD_VALUE, + ZK_STAGE2_DORY_REDUCE_PUBLIC_FOLD_VALUE, + STAGE3_PAYLOAD, + STAGE3_REDUCED_OPENINGS, + OPENING_CLAIM_POINT, + OPENING_CLAIM_EVAL, + HYRAX_OPENING_ROW, + HYRAX_OPENING_SCALAR, + DENSE_COMMITMENT, + PUBLIC_OUTPUT, + ZK_PUBLIC_OUTPUT, + NATIVE_FINAL_INPUT, + ZK_NATIVE_FINAL_INPUT, +]; diff --git a/crates/jolt-dory-assist-verifier/tests/soundness/tampering/openings.rs b/crates/jolt-dory-assist-verifier/tests/soundness/tampering/openings.rs new file mode 100644 index 0000000000..d884f92693 --- /dev/null +++ b/crates/jolt-dory-assist-verifier/tests/soundness/tampering/openings.rs @@ -0,0 +1,39 @@ +use crate::support::{ + assert_rejects, clear_base_case, tamper_dense_commitment, tamper_hyrax_opening_row, + tamper_hyrax_opening_scalar, tamper_opening_claim_eval, tamper_opening_claim_point, +}; + +#[test] +fn tampered_opening_claim_point_rejects() { + let mut case = clear_base_case(); + tamper_opening_claim_point(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_opening_claim_eval_rejects() { + let mut case = clear_base_case(); + tamper_opening_claim_eval(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_hyrax_opening_row_rejects() { + let mut case = clear_base_case(); + tamper_hyrax_opening_row(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_hyrax_opening_scalar_rejects() { + let mut case = clear_base_case(); + tamper_hyrax_opening_scalar(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_dense_commitment_rejects() { + let mut case = clear_base_case(); + tamper_dense_commitment(&mut case); + assert_rejects(case.verify_clear()); +} diff --git a/crates/jolt-dory-assist-verifier/tests/soundness/tampering/public_outputs.rs b/crates/jolt-dory-assist-verifier/tests/soundness/tampering/public_outputs.rs new file mode 100644 index 0000000000..2886cf5595 --- /dev/null +++ b/crates/jolt-dory-assist-verifier/tests/soundness/tampering/public_outputs.rs @@ -0,0 +1,31 @@ +use crate::support::{ + assert_rejects, clear_base_case, tamper_native_final_input, tamper_public_output, zk_base_case, +}; + +#[test] +fn tampered_public_output_rejects() { + let mut case = clear_base_case(); + tamper_public_output(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_zk_public_output_rejects() { + let mut case = zk_base_case(); + tamper_public_output(&mut case); + assert_rejects(case.verify_zk()); +} + +#[test] +fn tampered_native_final_input_rejects() { + let mut case = clear_base_case(); + tamper_native_final_input(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_zk_native_final_input_rejects() { + let mut case = zk_base_case(); + tamper_native_final_input(&mut case); + assert_rejects(case.verify_zk()); +} diff --git a/crates/jolt-dory-assist-verifier/tests/soundness/tampering/stages.rs b/crates/jolt-dory-assist-verifier/tests/soundness/tampering/stages.rs new file mode 100644 index 0000000000..b66ad00c60 --- /dev/null +++ b/crates/jolt-dory-assist-verifier/tests/soundness/tampering/stages.rs @@ -0,0 +1,406 @@ +use crate::support::{ + assert_accepts, assert_rejects, assert_rejects_at_stage, clear_base_case, + clear_multiround_case, clear_shift_public_kernel_case, tamper_stage1_accumulator_output, + tamper_stage1_boundary_output, tamper_stage1_boundary_public, + tamper_stage1_digit_selector_output, tamper_stage1_dory_reduce_boundary_output, + tamper_stage1_dory_reduce_g1_transition_output, tamper_stage1_dory_reduce_g2_transition_output, + tamper_stage1_dory_reduce_gt_transition_output, tamper_stage1_dory_reduce_scalar_fold_output, + tamper_stage1_dory_reduce_state_chain_output, tamper_stage1_g1_addition_output, + tamper_stage1_g1_boundary_public, tamper_stage1_g1_scalar_multiplication_output, + tamper_stage1_g1_shift_output, tamper_stage1_g2_addition_output, + tamper_stage1_g2_boundary_public, tamper_stage1_g2_scalar_multiplication_output, + tamper_stage1_g2_shift_output, tamper_stage1_line_evaluation_output, + tamper_stage1_line_step_output, tamper_stage1_miller_boundary_output, + tamper_stage1_multiplication_output, tamper_stage1_pair_product_output, tamper_stage1_payload, + tamper_stage1_relation_output, tamper_stage1_shift_output, tamper_stage1_shift_public, + tamper_stage1_sumcheck_round_count, tamper_stage2_accumulator_copy_value, + tamper_stage2_accumulator_quotient_copy_value, tamper_stage2_boundary_copy_value, + tamper_stage2_copy_value, tamper_stage2_dory_reduce_initial_state_copy_value, + tamper_stage2_dory_reduce_proof_artifact_copy_value, + tamper_stage2_dory_reduce_public_fold_value, tamper_stage2_dory_reduce_scalar_fold_copy_value, + tamper_stage2_dory_reduce_setup_artifact_copy_value, + tamper_stage2_dory_reduce_transcript_scalar_copy_value, tamper_stage2_g1_boundary_copy_value, + tamper_stage2_g1_shift_copy_value, tamper_stage2_g2_boundary_copy_value, + tamper_stage2_g2_shift_copy_value, tamper_stage2_line_copy_value, + tamper_stage2_pair_product_copy_value, tamper_stage2_pair_product_quotient_copy_value, + tamper_stage2_payload, tamper_stage2_public_vmv_c_copy_value, + tamper_stage2_public_vmv_e1_copy_value, tamper_stage2_zk_dory_reduce_public_fold_value, + tamper_stage3_payload, tamper_stage3_reduced_openings, zk_multiround_case, +}; +use jolt_dory_assist_verifier::DoryAssistStage; + +#[test] +fn tampered_stage1_payload_rejects() { + let mut case = clear_base_case(); + tamper_stage1_payload(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage1_sumcheck_round_count_rejects() { + let mut case = clear_base_case(); + tamper_stage1_sumcheck_round_count(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage1_relation_output_rejects() { + let mut case = clear_base_case(); + tamper_stage1_relation_output(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage1_digit_selector_output_rejects() { + let mut case = clear_base_case(); + tamper_stage1_digit_selector_output(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage1_shift_output_rejects() { + let mut case = clear_base_case(); + tamper_stage1_shift_output(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn stage1_shift_public_kernel_fixture_accepts() { + assert_accepts(clear_shift_public_kernel_case().verify_clear()); +} + +#[test] +fn tampered_stage1_shift_public_rejects() { + let mut case = clear_shift_public_kernel_case(); + tamper_stage1_shift_public(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage1_boundary_output_rejects() { + let mut case = clear_base_case(); + tamper_stage1_boundary_output(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage1_boundary_public_rejects() { + let mut case = clear_base_case(); + tamper_stage1_boundary_public(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage1_multiplication_output_rejects() { + let mut case = clear_base_case(); + tamper_stage1_multiplication_output(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage1_g1_scalar_multiplication_output_rejects() { + let mut case = clear_base_case(); + tamper_stage1_g1_scalar_multiplication_output(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage1_g1_shift_output_rejects() { + let mut case = clear_base_case(); + tamper_stage1_g1_shift_output(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage1_g1_boundary_public_rejects() { + let mut case = clear_base_case(); + tamper_stage1_g1_boundary_public(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage1_g1_addition_output_rejects() { + let mut case = clear_base_case(); + tamper_stage1_g1_addition_output(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage1_g2_scalar_multiplication_output_rejects() { + let mut case = clear_base_case(); + tamper_stage1_g2_scalar_multiplication_output(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage1_g2_shift_output_rejects() { + let mut case = clear_base_case(); + tamper_stage1_g2_shift_output(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage1_g2_boundary_public_rejects() { + let mut case = clear_base_case(); + tamper_stage1_g2_boundary_public(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage1_g2_addition_output_rejects() { + let mut case = clear_base_case(); + tamper_stage1_g2_addition_output(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage1_line_step_output_rejects() { + let mut case = clear_base_case(); + tamper_stage1_line_step_output(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage1_line_evaluation_output_rejects() { + let mut case = clear_base_case(); + tamper_stage1_line_evaluation_output(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage1_pair_product_output_rejects() { + let mut case = clear_base_case(); + tamper_stage1_pair_product_output(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage1_accumulator_output_rejects() { + let mut case = clear_base_case(); + tamper_stage1_accumulator_output(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage1_miller_boundary_output_rejects() { + let mut case = clear_base_case(); + tamper_stage1_miller_boundary_output(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage1_dory_reduce_gt_transition_output_rejects() { + let mut case = clear_base_case(); + tamper_stage1_dory_reduce_gt_transition_output(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage1_dory_reduce_g1_transition_output_rejects() { + let mut case = clear_base_case(); + tamper_stage1_dory_reduce_g1_transition_output(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage1_dory_reduce_g2_transition_output_rejects() { + let mut case = clear_base_case(); + tamper_stage1_dory_reduce_g2_transition_output(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage1_dory_reduce_scalar_fold_output_rejects() { + let mut case = clear_base_case(); + tamper_stage1_dory_reduce_scalar_fold_output(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage1_dory_reduce_state_chain_output_rejects_for_multiround() { + let mut case = clear_multiround_case(); + tamper_stage1_dory_reduce_state_chain_output(&mut case); + assert_rejects_at_stage(case.verify_clear(), DoryAssistStage::Stage1); +} + +#[test] +fn tampered_stage1_dory_reduce_boundary_output_rejects_for_multiround() { + let mut case = clear_multiround_case(); + tamper_stage1_dory_reduce_boundary_output(&mut case); + assert_rejects_at_stage(case.verify_clear(), DoryAssistStage::Stage1); +} + +#[test] +fn tampered_zk_stage1_dory_reduce_state_chain_output_rejects_for_multiround() { + let mut case = zk_multiround_case(); + tamper_stage1_dory_reduce_state_chain_output(&mut case); + assert_rejects_at_stage(case.verify_zk(), DoryAssistStage::Stage1); +} + +#[test] +fn tampered_zk_stage1_dory_reduce_boundary_output_rejects_for_multiround() { + let mut case = zk_multiround_case(); + tamper_stage1_dory_reduce_boundary_output(&mut case); + assert_rejects_at_stage(case.verify_zk(), DoryAssistStage::Stage1); +} + +#[test] +fn tampered_stage2_payload_rejects() { + let mut case = clear_base_case(); + tamper_stage2_payload(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage2_copy_value_rejects() { + let mut case = clear_base_case(); + tamper_stage2_copy_value(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage2_public_vmv_c_copy_value_rejects_at_stage2() { + let mut case = clear_base_case(); + tamper_stage2_public_vmv_c_copy_value(&mut case); + assert_rejects_at_stage(case.verify_clear(), DoryAssistStage::Stage2); +} + +#[test] +fn tampered_stage2_public_vmv_e1_copy_value_rejects_at_stage2() { + let mut case = clear_base_case(); + tamper_stage2_public_vmv_e1_copy_value(&mut case); + assert_rejects_at_stage(case.verify_clear(), DoryAssistStage::Stage2); +} + +#[test] +fn tampered_stage2_line_copy_value_rejects() { + let mut case = clear_base_case(); + tamper_stage2_line_copy_value(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage2_pair_product_copy_value_rejects() { + let mut case = clear_base_case(); + tamper_stage2_pair_product_copy_value(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage2_pair_product_quotient_copy_value_rejects() { + let mut case = clear_base_case(); + tamper_stage2_pair_product_quotient_copy_value(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage2_accumulator_copy_value_rejects() { + let mut case = clear_base_case(); + tamper_stage2_accumulator_copy_value(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage2_accumulator_quotient_copy_value_rejects() { + let mut case = clear_base_case(); + tamper_stage2_accumulator_quotient_copy_value(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage2_boundary_copy_value_rejects() { + let mut case = clear_base_case(); + tamper_stage2_boundary_copy_value(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage2_g1_shift_copy_value_rejects_at_stage2() { + let mut case = clear_base_case(); + tamper_stage2_g1_shift_copy_value(&mut case); + assert_rejects_at_stage(case.verify_clear(), DoryAssistStage::Stage2); +} + +#[test] +fn tampered_stage2_g1_boundary_copy_value_rejects_at_stage2() { + let mut case = clear_base_case(); + tamper_stage2_g1_boundary_copy_value(&mut case); + assert_rejects_at_stage(case.verify_clear(), DoryAssistStage::Stage2); +} + +#[test] +fn tampered_stage2_g2_shift_copy_value_rejects_at_stage2() { + let mut case = clear_base_case(); + tamper_stage2_g2_shift_copy_value(&mut case); + assert_rejects_at_stage(case.verify_clear(), DoryAssistStage::Stage2); +} + +#[test] +fn tampered_stage2_g2_boundary_copy_value_rejects_at_stage2() { + let mut case = clear_base_case(); + tamper_stage2_g2_boundary_copy_value(&mut case); + assert_rejects_at_stage(case.verify_clear(), DoryAssistStage::Stage2); +} + +#[test] +fn tampered_stage2_dory_reduce_scalar_fold_copy_value_rejects_at_stage2() { + let mut case = clear_base_case(); + tamper_stage2_dory_reduce_scalar_fold_copy_value(&mut case); + assert_rejects_at_stage(case.verify_clear(), DoryAssistStage::Stage2); +} + +#[test] +fn tampered_stage2_dory_reduce_initial_state_copy_value_rejects_at_stage2() { + let mut case = clear_base_case(); + tamper_stage2_dory_reduce_initial_state_copy_value(&mut case); + assert_rejects_at_stage(case.verify_clear(), DoryAssistStage::Stage2); +} + +#[test] +fn tampered_stage2_dory_reduce_proof_artifact_copy_value_rejects_at_stage2() { + let mut case = clear_base_case(); + tamper_stage2_dory_reduce_proof_artifact_copy_value(&mut case); + assert_rejects_at_stage(case.verify_clear(), DoryAssistStage::Stage2); +} + +#[test] +fn tampered_stage2_dory_reduce_setup_artifact_copy_value_rejects_at_stage2() { + let mut case = clear_base_case(); + tamper_stage2_dory_reduce_setup_artifact_copy_value(&mut case); + assert_rejects_at_stage(case.verify_clear(), DoryAssistStage::Stage2); +} + +#[test] +fn tampered_stage2_dory_reduce_transcript_scalar_copy_value_rejects_at_stage2() { + let mut case = clear_base_case(); + tamper_stage2_dory_reduce_transcript_scalar_copy_value(&mut case); + assert_rejects_at_stage(case.verify_clear(), DoryAssistStage::Stage2); +} + +#[test] +fn tampered_stage2_dory_reduce_public_fold_value_rejects_at_stage2_for_multiround() { + let mut case = clear_multiround_case(); + tamper_stage2_dory_reduce_public_fold_value(&mut case); + assert_rejects_at_stage(case.verify_clear(), DoryAssistStage::Stage2); +} + +#[test] +fn tampered_zk_stage2_dory_reduce_public_fold_value_rejects_at_stage2_for_multiround() { + let mut case = zk_multiround_case(); + tamper_stage2_zk_dory_reduce_public_fold_value(&mut case); + assert_rejects_at_stage(case.verify_zk(), DoryAssistStage::Stage2); +} + +#[test] +fn tampered_stage3_payload_rejects() { + let mut case = clear_base_case(); + tamper_stage3_payload(&mut case); + assert_rejects(case.verify_clear()); +} + +#[test] +fn tampered_stage3_reduced_openings_rejects() { + let mut case = clear_base_case(); + tamper_stage3_reduced_openings(&mut case); + assert_rejects(case.verify_clear()); +} diff --git a/crates/jolt-dory-assist-verifier/tests/support/mod.rs b/crates/jolt-dory-assist-verifier/tests/support/mod.rs new file mode 100644 index 0000000000..ed4a8cdaf2 --- /dev/null +++ b/crates/jolt-dory-assist-verifier/tests/support/mod.rs @@ -0,0 +1,2637 @@ +#![expect( + clippy::expect_used, + clippy::panic, + reason = "test fixtures may panic on invalid local setup" +)] + +use jolt_claims::protocols::dory_assist::{ + formulas::{ + composition, dory_reduce, + protocol::{protocol_claims, CANONICAL_RELATION_ORDER}, + transcript_scalars, + }, + DoryAssistChallengeId, DoryAssistCopyConstraint, DoryAssistDimensions, DoryAssistOpeningId, + DoryAssistRelationId, DoryAssistValueRef, DoryAssistVirtualPolynomial, DoryReduceDimensions, + DoryReducePolynomial, GtDimensions, PrefixPackingDimensions, +}; +use jolt_crypto::{Bn254Fq12, Bn254G1, Bn254G2, Bn254GT, Grumpkin, JoltGroup}; +use jolt_dory::{DoryCommitment, DoryProof, DoryScheme, DoryVerifierSetup}; +use jolt_dory_assist_verifier::{ + artifacts::{ + DoryProofArtifactLayout, DORY_PROOF_DIGEST_INDEX, DORY_REDUCE_ROUNDS_START, + DORY_VMV_C_START, DORY_VMV_E1_START, DORY_ZK_E2_START, GT_ARTIFACT_COEFFS, + }, + derive_hyrax_prover_setup, + native_final::{ + transparent_native_final_input_claims, transparent_replayed_final_pairing_check, + zk_native_final_input_claims, zk_replayed_final_pairing_check, + }, + proof::{NATIVE_FINAL_D1_START, NATIVE_FINAL_GT_C_START}, + verify_clear, verify_zk, ClearOpeningStatement, DoryAssist, DoryAssistConfig, DoryAssistHyrax, + DoryAssistInputPublicClaims, DoryAssistOpeningClaim, DoryAssistProof, DoryAssistStage, + DoryAssistStage1Proof, DoryAssistStage2Proof, DoryAssistVerifierError, ZkOpeningStatement, +}; +use jolt_field::{CanonicalBytes, FixedByteSize, Fq, Fr, FromPrimitiveInt, Invertible}; +use jolt_hyrax::HyraxDimensions; +use jolt_openings::{CommitmentScheme, ZkOpeningScheme}; +use jolt_poly::{EqPolynomial, Polynomial}; +use jolt_sumcheck::SUMCHECK_ROUND_TRANSCRIPT_LABEL; +use jolt_transcript::{Blake2bTranscript, Label, LabelWithCount, Transcript, U64Word}; +use jolt_verifier::{PcsAssistClearInput, PcsAssistZkInput, PcsProofAssist}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum VerifierPhase { + CheckedInputs, + Stage1, + Stage2, + Stage3, + Opening, + NativeOutput, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum FixtureId { + ClearBase, + ClearMultiround, + ZkBase, + ZkMultiround, + ClearInputMismatch, + ZkInputMismatch, + StagePayloadMismatch, + OpeningClaimMismatch, + HyraxOpeningMismatch, + DenseCommitmentMismatch, + PublicOutputMismatch, + ZkPublicOutputMismatch, + NativeFinalInputMismatch, + ZkNativeFinalInputMismatch, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct TestCase { + pub name: &'static str, + pub zk: bool, + pub fixture: FixtureId, + pub checked_at: VerifierPhase, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct FixtureMetadata { + pub id: FixtureId, + pub name: &'static str, + pub zk: bool, + pub expected_accepts: bool, + pub notes: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct TamperTarget { + pub name: &'static str, + pub fixture: FixtureId, + pub checked_at: VerifierPhase, + pub coverage_note: &'static str, +} + +pub struct DoryAssistVerifierCase { + pub verifier_setup: DoryVerifierSetup, + pub pcs_proof: DoryProof, + pub commitment: DoryCommitment, + pub point: Vec, + pub eval: Fr, + pub assist_proof: DoryAssistProof, +} + +impl DoryAssistVerifierCase { + pub fn clear_input(&self) -> PcsAssistClearInput<'_, DoryScheme> { + PcsAssistClearInput { + setup: &self.verifier_setup, + pcs_proof: &self.pcs_proof, + commitment: &self.commitment, + point: &self.point, + eval: self.eval, + } + } + + pub fn zk_input(&self) -> PcsAssistZkInput<'_, DoryScheme> { + PcsAssistZkInput { + setup: &self.verifier_setup, + pcs_proof: &self.pcs_proof, + commitment: &self.commitment, + point: &self.point, + } + } + + pub fn verify_clear(&self) -> Result<(), DoryAssistVerifierError> { + self.verify_clear_with_transcript::>() + } + + pub fn verify_clear_with_transcript(&self) -> Result<(), DoryAssistVerifierError> + where + T: Transcript, + { + let mut transcript = T::new(b"dory-assist-oracle"); + verify_clear( + &DoryAssistConfig, + self.clear_input(), + &self.assist_proof, + &mut transcript, + ) + } + + pub fn verify_zk(&self) -> Result { + self.verify_zk_with_transcript::>() + } + + pub fn verify_zk_with_transcript( + &self, + ) -> Result + where + T: Transcript, + { + let mut transcript = T::new(b"dory-assist-oracle"); + verify_zk( + &DoryAssistConfig, + self.zk_input(), + &self.assist_proof, + &mut transcript, + ) + } + + pub fn verify_clear_via_pcs_assist(&self) -> Result<(), DoryAssistVerifierError> { + self.verify_clear_via_pcs_assist_with_transcript::>() + } + + pub fn verify_clear_via_pcs_assist_with_transcript( + &self, + ) -> Result<(), DoryAssistVerifierError> + where + T: Transcript, + { + let mut transcript = T::new(b"dory-assist-oracle"); + >::verify_clear( + &DoryAssistConfig, + self.clear_input(), + &self.assist_proof, + &mut transcript, + ) + } + + pub fn verify_zk_via_pcs_assist( + &self, + ) -> Result { + self.verify_zk_via_pcs_assist_with_transcript::>() + } + + pub fn verify_zk_via_pcs_assist_with_transcript( + &self, + ) -> Result + where + T: Transcript, + { + let mut transcript = T::new(b"dory-assist-oracle"); + >::verify_zk( + &DoryAssistConfig, + self.zk_input(), + &self.assist_proof, + &mut transcript, + ) + } +} + +pub fn clear_base_case() -> DoryAssistVerifierCase { + base_case(false) +} + +pub fn clear_multiround_case() -> DoryAssistVerifierCase { + base_case_with_num_vars::>(false, 4) +} + +pub fn clear_multiround_case_with_transcript() -> DoryAssistVerifierCase +where + T: Transcript, +{ + base_case_with_num_vars::(false, 4) +} + +pub fn clear_base_case_with_transcript() -> DoryAssistVerifierCase +where + T: Transcript, +{ + base_case_with_transcript::(false) +} + +pub fn clear_shift_public_kernel_case() -> DoryAssistVerifierCase { + let mut case = base_case(false); + let vmv_c0 = case + .assist_proof + .claims + .stage1 + .public + .input + .dory_proof_artifacts[DORY_VMV_C_START]; + case.assist_proof + .claims + .stage1 + .gt_exponentiation + .accumulator = vmv_c0; + case.assist_proof + .claims + .stage1 + .gt_exponentiation_shift + .accumulator = vmv_c0; + case.assist_proof + .claims + .stage1 + .gt_exponentiation_boundary + .accumulator = vmv_c0; + case.assist_proof.claims.stage1.public.gt_shift_eq_kernel = Fq::default(); + case.assist_proof + .claims + .stage1 + .public + .gt_exponentiation_boundary + .initial_value = vmv_c0; + bind_native_public_output_fixture(&mut case, false); + case +} + +pub fn zk_base_case() -> DoryAssistVerifierCase { + base_case(true) +} + +pub fn zk_multiround_case() -> DoryAssistVerifierCase { + base_case_with_num_vars::>(true, 4) +} + +pub fn zk_multiround_case_with_transcript() -> DoryAssistVerifierCase +where + T: Transcript, +{ + base_case_with_num_vars::(true, 4) +} + +pub fn zk_base_case_with_transcript() -> DoryAssistVerifierCase +where + T: Transcript, +{ + base_case_with_transcript::(true) +} + +pub fn assert_unique_case_names(cases: &[TestCase]) { + for (index, case) in cases.iter().enumerate() { + for other in &cases[index + 1..] { + assert_ne!(case.name, other.name, "duplicate test case name"); + } + } +} + +pub fn assert_unique_tamper_target_names(targets: &[TamperTarget]) { + for (index, target) in targets.iter().enumerate() { + for other in &targets[index + 1..] { + assert_ne!(target.name, other.name, "duplicate tamper target name"); + } + } +} + +pub fn assert_case_metadata_matches(case: TestCase, metadata: FixtureMetadata) { + assert_eq!(case.fixture, metadata.id); + assert_eq!(case.zk, metadata.zk); +} + +pub fn assert_accepts(result: Result) { + assert!( + result.is_ok(), + "valid assist proof was rejected: {result:?}" + ); +} + +pub fn assert_rejects(result: Result) { + let result_debug = format!("{result:?}"); + + assert!( + result.is_err(), + "tampered assist proof was accepted: {result_debug}", + ); +} + +pub fn assert_rejects_at_stage( + result: Result, + expected_stage: DoryAssistStage, +) { + let result_debug = format!("{result:?}"); + let actual_stage = match result { + Err( + DoryAssistVerifierError::StageClaimMismatch { stage, .. } + | DoryAssistVerifierError::StageSumcheckFailed { stage, .. } + | DoryAssistVerifierError::StageOutputMismatch { stage, .. }, + ) => Some(stage), + Err(_) | Ok(_) => None, + }; + + assert_eq!( + actual_stage, + Some(expected_stage), + "tampered assist proof was not rejected at {expected_stage}: {result_debug}", + ); +} + +pub fn tamper_clear_eval(case: &mut DoryAssistVerifierCase) { + case.eval += Fr::from_u64(1); +} + +pub fn tamper_opening_point(case: &mut DoryAssistVerifierCase) { + case.point[0] += Fr::from_u64(1); +} + +pub fn tamper_checked_input_digest(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .public + .input + .checked_input_digest += Fq::from_u64(1); +} + +pub fn tamper_verifier_setup_digest(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .public + .input + .verifier_setup_digest += Fq::from_u64(1); +} + +pub fn tamper_verifier_setup_artifact(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .public + .input + .verifier_setup_artifacts[0] += Fq::from_u64(1); +} + +pub fn tamper_dory_proof_artifact(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .public + .input + .dory_proof_artifacts[DORY_PROOF_DIGEST_INDEX] += Fq::from_u64(1); +} + +pub fn tamper_dory_vmv_c_artifact(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .public + .input + .dory_proof_artifacts[DORY_VMV_C_START] += Fq::from_u64(1); +} + +pub fn tamper_dory_vmv_e1_artifact(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .public + .input + .dory_proof_artifacts[DORY_VMV_E1_START] += Fq::from_u64(1); +} + +pub fn tamper_dory_zk_artifact(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .public + .input + .dory_proof_artifacts[DORY_ZK_E2_START] += Fq::from_u64(1); +} + +pub fn tamper_dory_zk_y_com_artifact(case: &mut DoryAssistVerifierCase) { + let layout = DoryProofArtifactLayout::for_proof(&case.pcs_proof); + case.assist_proof + .claims + .stage1 + .public + .input + .dory_proof_artifacts[layout.zk_y_com().start] += Fq::from_u64(1); +} + +pub fn tamper_dory_scalar_product_artifact(case: &mut DoryAssistVerifierCase) { + let layout = DoryProofArtifactLayout::for_proof(&case.pcs_proof); + case.assist_proof + .claims + .stage1 + .public + .input + .dory_proof_artifacts[layout.scalar_product_p1().start] += Fq::from_u64(1); +} + +pub fn tamper_dory_reduce_round_artifact(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .public + .input + .dory_proof_artifacts[DORY_REDUCE_ROUNDS_START] += Fq::from_u64(1); +} + +pub fn tamper_dory_reduce_dimensions(case: &mut DoryAssistVerifierCase) { + let dimensions = case.assist_proof.dimensions.dory_reduce; + case.assist_proof.dimensions.dory_reduce = + DoryReduceDimensions::new(dimensions.point_len(), dimensions.reduce_rounds() + 1); +} + +pub fn tamper_gt_dimensions(case: &mut DoryAssistVerifierCase) { + let dimensions = case.assist_proof.dimensions.gt; + case.assist_proof.dimensions.gt = GtDimensions::new( + dimensions.exp_step_vars() + 1, + dimensions.exp_instance_vars(), + dimensions.mul_instance_vars(), + ); +} + +pub fn tamper_packing_dimensions(case: &mut DoryAssistVerifierCase) { + let dimensions = case.assist_proof.dimensions.packing; + case.assist_proof.dimensions.packing = PrefixPackingDimensions::new( + dimensions.packed_vars() + 1, + dimensions.max_poly_vars(), + dimensions.num_claims(), + ) + .expect("valid non-minimal packing dimensions"); +} + +pub fn tamper_dory_final_artifact(case: &mut DoryAssistVerifierCase) { + let artifacts = &mut case + .assist_proof + .claims + .stage1 + .public + .input + .dory_proof_artifacts; + let layout = DoryProofArtifactLayout::for_proof(&case.pcs_proof); + artifacts[layout.final_e2_start()] += Fq::from_u64(1); +} + +pub fn tamper_jolt_commitment_claim(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .public + .input + .jolt_commitments[0] += Fq::from_u64(1); +} + +pub fn tamper_jolt_commitment_gt_claim(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .public + .input + .jolt_commitments[1] += Fq::from_u64(1); +} + +pub fn tamper_jolt_evaluation_claim(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .public + .input + .jolt_evaluation_claims[0] += Fq::from_u64(1); +} + +pub fn tamper_transcript_scalar_claim(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .public + .input + .transcript_scalars[0] += Fq::from_u64(1); +} + +pub fn tamper_zk_sigma_c_transcript_scalar_claim(case: &mut DoryAssistVerifierCase) { + let index = transcript_scalars::dory_scalar_product_sigma_c( + case.point.len(), + case.pcs_proof.reduce_round_count(), + ); + case.assist_proof + .claims + .stage1 + .public + .input + .transcript_scalars[index] += Fq::from_u64(1); +} + +pub fn tamper_stage1_payload(case: &mut DoryAssistVerifierCase) { + case.assist_proof.stages.stage1.relations[0].sumcheck.degree += 1; +} + +pub fn tamper_stage1_sumcheck_round_count(case: &mut DoryAssistVerifierCase) { + let _ = case.assist_proof.stages.stage1.relations[0] + .sumcheck_proof + .round_polynomials + .pop(); +} + +pub fn tamper_stage1_relation_output(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .gt_exponentiation + .accumulator = Fq::from_u64(1); + case.assist_proof + .claims + .stage1 + .gt_exponentiation + .digit_selector = Fq::from_u64(1); +} + +pub fn tamper_stage1_digit_selector_output(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .gt_exponentiation_digit_selector + .digit_lo = Fq::default(); +} + +pub fn tamper_stage1_shift_output(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .gt_exponentiation_shift + .accumulator = Fq::from_u64(1); +} + +pub fn tamper_stage1_shift_public(case: &mut DoryAssistVerifierCase) { + case.assist_proof.claims.stage1.public.gt_shift_eq_kernel = Fq::from_u64(1); +} + +pub fn tamper_stage1_boundary_output(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .gt_exponentiation_boundary + .accumulator = Fq::from_u64(1); +} + +pub fn tamper_stage1_boundary_public(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .public + .gt_exponentiation_boundary + .initial_value = Fq::from_u64(1); +} + +pub fn tamper_stage1_multiplication_output(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .gt_multiplication + .opening + .output = Fq::from_u64(1); +} + +pub fn tamper_stage1_g1_scalar_multiplication_output(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .g1 + .scalar_multiplication + .doubled + .x = Fq::from_u64(1); +} + +pub fn tamper_stage1_g1_shift_output(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .g1 + .scalar_multiplication_shift + .accumulator + .x = Fq::from_u64(1); +} + +pub fn tamper_stage1_g1_boundary_public(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .public + .g1 + .scalar_multiplication_boundary + .initial_value + .x = Fq::from_u64(1); +} + +pub fn tamper_stage1_g1_addition_output(case: &mut DoryAssistVerifierCase) { + case.assist_proof.claims.stage1.g1.addition.output.x = Fq::from_u64(1); +} + +pub fn tamper_stage1_g2_scalar_multiplication_output(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .g2 + .scalar_multiplication + .doubled + .x[0] = Fq::from_u64(1); +} + +pub fn tamper_stage1_g2_shift_output(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .g2 + .scalar_multiplication_shift + .accumulator + .x[0] = Fq::from_u64(1); +} + +pub fn tamper_stage1_g2_boundary_public(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .public + .g2 + .scalar_multiplication_boundary + .initial_value + .x[0] = Fq::from_u64(1); +} + +pub fn tamper_stage1_g2_addition_output(case: &mut DoryAssistVerifierCase) { + case.assist_proof.claims.stage1.g2.addition.output.x[0] = Fq::from_u64(1); +} + +pub fn tamper_stage1_line_step_output(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .miller_loop + .line_step + .shifted_state_x[0] = Fq::from_u64(1); +} + +pub fn tamper_stage1_line_evaluation_output(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .miller_loop + .line_evaluation + .line_evaluation_coeffs[6] = Fq::from_u64(1); +} + +pub fn tamper_stage1_pair_product_output(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .miller_loop + .pair_product + .shifted_accumulator[0] = Fq::from_u64(1); +} + +pub fn tamper_stage1_accumulator_output(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .miller_loop + .accumulator + .accumulator[0] = Fq::from_u64(1); +} + +pub fn tamper_stage1_miller_boundary_output(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .miller_loop + .boundary + .accumulator[0] = Fq::from_u64(1); +} + +pub fn tamper_stage1_dory_reduce_scalar_fold_output(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .dory_reduce + .scalar_fold + .s1_next_accumulator = Fq::from_u64(1); +} + +pub fn tamper_stage1_dory_reduce_state_chain_output(case: &mut DoryAssistVerifierCase) { + set_dory_reduce_opening( + &mut case.assist_proof, + dory_reduce_opening( + DoryAssistRelationId::DoryReduceStateChain, + DoryReducePolynomial::S1Accumulator, + ), + Fq::from_u64(7), + ); +} + +pub fn tamper_stage1_dory_reduce_boundary_output(case: &mut DoryAssistVerifierCase) { + set_dory_reduce_opening( + &mut case.assist_proof, + dory_reduce_opening( + DoryAssistRelationId::DoryReduceBoundary, + DoryReducePolynomial::S1Accumulator, + ), + Fq::from_u64(9), + ); +} + +pub fn tamper_stage1_dory_reduce_gt_transition_output(case: &mut DoryAssistVerifierCase) { + tamper_dory_reduce_transition_claim( + case, + DoryAssistRelationId::DoryReduceGtTransition, + DoryReducePolynomial::NextC(0), + ); +} + +pub fn tamper_stage1_dory_reduce_g1_transition_output(case: &mut DoryAssistVerifierCase) { + tamper_dory_reduce_transition_claim( + case, + DoryAssistRelationId::DoryReduceG1Transition, + DoryReducePolynomial::NextE1X, + ); +} + +pub fn tamper_stage1_dory_reduce_g2_transition_output(case: &mut DoryAssistVerifierCase) { + tamper_dory_reduce_transition_claim( + case, + DoryAssistRelationId::DoryReduceG2Transition, + DoryReducePolynomial::NextE2X0, + ); +} + +fn tamper_dory_reduce_transition_claim( + case: &mut DoryAssistVerifierCase, + relation: DoryAssistRelationId, + polynomial: DoryReducePolynomial, +) { + let id = DoryAssistOpeningId::virtual_polynomial( + DoryAssistVirtualPolynomial::DoryReduce(polynomial), + relation, + ); + let claim = case + .assist_proof + .claims + .stage1 + .dory_reduce + .transitions + .iter_mut() + .find(|claim| claim.id == id) + .expect("fixture contains Dory-reduce transition claim"); + claim.value = Fq::from_u64(1); +} + +pub fn tamper_stage2_payload(case: &mut DoryAssistVerifierCase) { + let _ = case.assist_proof.stages.stage2.copy_constraints.pop(); +} + +pub fn tamper_stage2_copy_value(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .gt_exponentiation_digit_bitness + .digit_lo = Fq::default(); +} + +pub fn tamper_stage2_public_vmv_c_copy_value(case: &mut DoryAssistVerifierCase) { + let changed = case + .assist_proof + .claims + .stage1 + .public + .input + .dory_proof_artifacts[DORY_VMV_C_START] + + Fq::from_u64(1); + case.assist_proof + .claims + .stage1 + .gt_exponentiation + .accumulator = changed; + case.assist_proof + .claims + .stage1 + .gt_exponentiation_shift + .accumulator = changed; + case.assist_proof + .claims + .stage1 + .gt_exponentiation_boundary + .accumulator = changed; + case.assist_proof + .claims + .stage1 + .public + .gt_exponentiation_boundary + .initial_value = changed; + case.assist_proof.claims.stage1.public.gt_shift_eq_kernel = Fq::default(); + case.assist_proof + .claims + .stage1 + .public + .miller_loop + .accumulator_shift_eq_kernel = + accumulator_zero_sumcheck_kernel::>(case, false); + rebalance_dory_reduce_transition_relations(case, false); +} + +pub fn tamper_stage2_public_vmv_e1_copy_value(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .miller_loop + .line_evaluation + .g1_point_x = case + .assist_proof + .claims + .stage1 + .public + .input + .dory_proof_artifacts[DORY_VMV_E1_START] + + Fq::from_u64(1); + case.assist_proof + .claims + .stage1 + .public + .miller_loop + .accumulator_shift_eq_kernel = + accumulator_zero_sumcheck_kernel::>(case, false); + rebalance_dory_reduce_transition_relations(case, false); +} + +pub fn tamper_stage2_line_copy_value(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .miller_loop + .line_evaluation + .line_coefficients[0][0] = Fq::from_u64(1); +} + +pub fn tamper_stage2_pair_product_copy_value(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .miller_loop + .pair_product + .accumulator[0] = Fq::from_u64(1); + case.assist_proof + .claims + .stage1 + .public + .miller_loop + .pair_product_shift_eq_kernel = Fq::default(); + case.assist_proof + .claims + .stage1 + .public + .miller_loop + .pair_product_initial_selector = Fq::default(); + case.assist_proof + .claims + .stage1 + .public + .miller_loop + .pair_product_final_selector = Fq::default(); +} + +pub fn tamper_stage2_pair_product_quotient_copy_value(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .miller_loop + .pair_product + .quotient[0] = Fq::from_u64(1); +} + +pub fn tamper_stage2_accumulator_copy_value(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .miller_loop + .accumulator + .accumulator[0] = Fq::from_u64(1); + case.assist_proof + .claims + .stage1 + .public + .miller_loop + .accumulator_shift_eq_kernel = Fq::default(); +} + +pub fn tamper_stage2_accumulator_quotient_copy_value(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .miller_loop + .accumulator + .quotient[0] = Fq::from_u64(1); +} + +pub fn tamper_stage2_boundary_copy_value(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .miller_loop + .boundary + .accumulator[0] = Fq::from_u64(1); + case.assist_proof + .claims + .stage1 + .public + .miller_loop + .boundary_initial_selector = Fq::default(); +} + +pub fn tamper_stage2_g1_shift_copy_value(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .g1 + .scalar_multiplication_shift + .shifted_accumulator + .x = Fq::from_u64(1); + let final_claim = stage1_zero_sumcheck_final_claim( + case, + false, + DoryAssistRelationId::G1ScalarMultiplicationShift, + ); + case.assist_proof + .claims + .stage1 + .g1 + .scalar_multiplication_shift + .accumulator + .x = final_claim; + case.assist_proof + .claims + .stage1 + .public + .miller_loop + .accumulator_shift_eq_kernel = + accumulator_zero_sumcheck_kernel::>(case, false); + rebalance_dory_reduce_transition_relations(case, false); +} + +pub fn tamper_stage2_g1_boundary_copy_value(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .g1 + .scalar_multiplication_boundary + .accumulator + .infinity = Fq::from_u64(1); + case.assist_proof + .claims + .stage1 + .public + .g1 + .scalar_multiplication_boundary + .initial_value + .infinity = Fq::from_u64(1); + case.assist_proof + .claims + .stage1 + .public + .miller_loop + .accumulator_shift_eq_kernel = + accumulator_zero_sumcheck_kernel::>(case, false); + rebalance_dory_reduce_transition_relations(case, false); +} + +pub fn tamper_stage2_g2_shift_copy_value(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .g2 + .scalar_multiplication_shift + .shifted_accumulator + .x[0] = Fq::from_u64(1); + let final_claim = stage1_zero_sumcheck_final_claim( + case, + false, + DoryAssistRelationId::G2ScalarMultiplicationShift, + ); + case.assist_proof + .claims + .stage1 + .g2 + .scalar_multiplication_shift + .accumulator + .x[0] = final_claim; + case.assist_proof + .claims + .stage1 + .public + .miller_loop + .accumulator_shift_eq_kernel = + accumulator_zero_sumcheck_kernel::>(case, false); + rebalance_dory_reduce_transition_relations(case, false); +} + +pub fn tamper_stage2_g2_boundary_copy_value(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .g2 + .scalar_multiplication_boundary + .accumulator + .infinity = Fq::from_u64(1); + case.assist_proof + .claims + .stage1 + .public + .g2 + .scalar_multiplication_boundary + .initial_value + .infinity = Fq::from_u64(1); + case.assist_proof + .claims + .stage1 + .public + .miller_loop + .accumulator_shift_eq_kernel = + accumulator_zero_sumcheck_kernel::>(case, false); + rebalance_dory_reduce_transition_relations(case, false); +} + +pub fn tamper_stage2_dory_reduce_scalar_fold_copy_value(case: &mut DoryAssistVerifierCase) { + case.assist_proof + .claims + .stage1 + .dory_reduce + .scalar_fold + .s1_fold_factor += Fq::from_u64(1); + rebalance_dory_reduce_scalar_fold_relation::>(case, false); +} + +pub fn tamper_stage2_dory_reduce_initial_state_copy_value(case: &mut DoryAssistVerifierCase) { + add_to_dory_reduce_transition_opening( + &mut case.assist_proof, + dory_reduce_opening( + DoryAssistRelationId::DoryReduceGtTransition, + DoryReducePolynomial::CurrentC(0), + ), + Fq::from_u64(1), + ); + rebalance_dory_reduce_transition_relations(case, false); +} + +pub fn tamper_stage2_dory_reduce_proof_artifact_copy_value(case: &mut DoryAssistVerifierCase) { + add_to_dory_reduce_transition_opening( + &mut case.assist_proof, + dory_reduce_opening( + DoryAssistRelationId::DoryReduceGtTransition, + DoryReducePolynomial::MessageD1Left(0), + ), + Fq::from_u64(1), + ); + rebalance_dory_reduce_transition_relations(case, false); +} + +pub fn tamper_stage2_dory_reduce_setup_artifact_copy_value(case: &mut DoryAssistVerifierCase) { + add_to_dory_reduce_transition_opening( + &mut case.assist_proof, + dory_reduce_opening( + DoryAssistRelationId::DoryReduceGtTransition, + DoryReducePolynomial::SetupChi(0), + ), + Fq::from_u64(1), + ); + rebalance_dory_reduce_transition_relations(case, false); +} + +pub fn tamper_stage2_dory_reduce_transcript_scalar_copy_value(case: &mut DoryAssistVerifierCase) { + add_to_dory_reduce_transition_opening( + &mut case.assist_proof, + dory_reduce_opening( + DoryAssistRelationId::DoryReduceGtTransition, + DoryReducePolynomial::Beta, + ), + Fq::from_u64(1), + ); + rebalance_dory_reduce_transition_relations(case, false); +} + +pub fn tamper_stage2_dory_reduce_public_fold_value(case: &mut DoryAssistVerifierCase) { + tamper_stage2_dory_reduce_public_fold_value_for_mode(case, false); +} + +pub fn tamper_stage2_zk_dory_reduce_public_fold_value(case: &mut DoryAssistVerifierCase) { + tamper_stage2_dory_reduce_public_fold_value_for_mode(case, true); +} + +fn tamper_stage2_dory_reduce_public_fold_value_for_mode( + case: &mut DoryAssistVerifierCase, + zk: bool, +) { + let opening = dory_reduce::s1_fold_factor_opening(); + let tampered = get_dory_reduce_opening(&case.assist_proof, opening) + Fq::from_u64(1); + set_dory_reduce_opening(&mut case.assist_proof, opening, tampered); + rebalance_dory_reduce_scalar_fold_relation::>(case, zk); +} + +pub fn tamper_stage3_payload(case: &mut DoryAssistVerifierCase) { + case.assist_proof.stages.stage3.packed_eval += Fq::from_u64(1); +} + +pub fn tamper_stage3_reduced_openings(case: &mut DoryAssistVerifierCase) { + case.assist_proof.stages.stage3.reduced_openings.swap(0, 1); +} + +pub fn tamper_opening_claim_point(case: &mut DoryAssistVerifierCase) { + case.assist_proof.claims.opening.packed_point[0] += Fq::from_u64(1); +} + +pub fn tamper_opening_claim_eval(case: &mut DoryAssistVerifierCase) { + case.assist_proof.claims.opening.packed_eval += Fq::from_u64(1); +} + +pub fn tamper_hyrax_opening_row(case: &mut DoryAssistVerifierCase) { + case.assist_proof.opening_proof.combined_row[0] += Fq::from_u64(1); +} + +pub fn tamper_hyrax_opening_scalar(case: &mut DoryAssistVerifierCase) { + case.assist_proof.opening_proof.combined_row_opening_scalar += Fq::from_u64(1); +} + +pub fn tamper_dense_commitment(case: &mut DoryAssistVerifierCase) { + case.assist_proof.dense_commitment.rows[0] = + Grumpkin::generator().scalar_mul(&Fq::from_u64(31)); +} + +pub fn tamper_public_output(case: &mut DoryAssistVerifierCase) { + case.assist_proof.public_outputs.pre_final_exponentiation = Bn254Fq12::default(); +} + +pub fn tamper_native_final_input(case: &mut DoryAssistVerifierCase) { + let inputs = &mut case.assist_proof.claims.stage1.public.native_final.inputs; + let identity = Bn254GT::identity().fq12_coefficients(); + let current_is_identity = inputs + [NATIVE_FINAL_GT_C_START..NATIVE_FINAL_GT_C_START + Bn254GT::FQ12_COEFFICIENTS] + .iter() + .copied() + .eq(identity); + if current_is_identity { + inputs.copy_within( + NATIVE_FINAL_D1_START..NATIVE_FINAL_D1_START + GT_ARTIFACT_COEFFS, + NATIVE_FINAL_GT_C_START, + ); + return; + } + + inputs[NATIVE_FINAL_GT_C_START..NATIVE_FINAL_GT_C_START + Bn254GT::FQ12_COEFFICIENTS] + .copy_from_slice(&identity); + inputs[NATIVE_FINAL_GT_C_START + Bn254GT::FQ12_COEFFICIENTS + ..NATIVE_FINAL_GT_C_START + GT_ARTIFACT_COEFFS] + .fill(Fq::default()); +} + +fn base_case(zk: bool) -> DoryAssistVerifierCase { + base_case_with_transcript::>(zk) +} + +fn base_case_with_transcript(zk: bool) -> DoryAssistVerifierCase +where + T: Transcript, +{ + base_case_with_num_vars::(zk, 2) +} + +fn base_case_with_num_vars(zk: bool, num_vars: usize) -> DoryAssistVerifierCase +where + T: Transcript, +{ + let (prover_setup, verifier_setup) = DoryScheme::setup(num_vars); + let poly = Polynomial::::from( + (0..(1usize << num_vars)) + .map(|index| Fr::from_u64(1 + index as u64)) + .collect::>(), + ); + let point = (0..num_vars) + .map(|index| Fr::from_u64(5 + 2 * index as u64)) + .collect::>(); + let eval = poly.evaluate(&point); + let mut transcript = T::new(b"dory-assist-oracle"); + let (commitment, pcs_proof) = if zk { + let (commitment, hint) = + ::commit_zk(poly.evaluations(), &prover_setup); + let (proof, _hiding_commitment, _blind) = + DoryScheme::open_zk(&poly, &point, eval, &prover_setup, hint, &mut transcript); + (commitment, proof) + } else { + let (commitment, hint) = DoryScheme::commit(poly.evaluations(), &prover_setup); + let proof = DoryScheme::open( + &poly, + &point, + eval, + &prover_setup, + Some(hint), + &mut transcript, + ); + (commitment, proof) + }; + + let dimensions = + dory_assist_dimensions_for_opening(point.len(), pcs_proof.reduce_round_count()); + let mut assist_proof = DoryAssistProof { + dimensions, + ..DoryAssistProof::default() + }; + assist_proof.stages.stage1 = + DoryAssistStage1Proof::canonical_for_dimensions(assist_proof.dimensions); + assist_proof.stages.stage2 = + DoryAssistStage2Proof::canonical_for_dimensions(assist_proof.dimensions); + bind_zero_dory_reduce_transition_fixture(&mut assist_proof); + populate_valid_hyrax_opening(&mut assist_proof); + + let mut case = DoryAssistVerifierCase { + verifier_setup, + pcs_proof, + commitment, + point, + eval, + assist_proof, + }; + bind_checked_input_public_claims_fixture_with_transcript::(&mut case, zk); + bind_native_public_output_fixture_with_transcript::(&mut case, zk); + case +} + +fn dory_assist_dimensions_for_opening( + point_len: usize, + reduce_rounds: usize, +) -> DoryAssistDimensions { + let supported = jolt_dory_assist_verifier::proof::default_dory_assist_dimensions(); + let unpacked = DoryAssistDimensions::new( + supported.gt, + supported.g1, + supported.g2, + supported.miller_loop, + DoryReduceDimensions::new(point_len, reduce_rounds), + supported.wiring, + PrefixPackingDimensions::new(0, 0, 0).expect("valid empty packing dimensions"), + ); + let packing = composition::prefix_packing_catalog(unpacked) + .minimal_dimensions() + .expect("valid checked Dory-assist packing dimensions"); + + DoryAssistDimensions::new( + unpacked.gt, + unpacked.g1, + unpacked.g2, + unpacked.miller_loop, + unpacked.dory_reduce, + unpacked.wiring, + packing, + ) +} + +fn bind_checked_input_public_claims_fixture(case: &mut DoryAssistVerifierCase, zk: bool) { + bind_checked_input_public_claims_fixture_with_transcript::>(case, zk); +} + +fn bind_checked_input_public_claims_fixture_with_transcript( + case: &mut DoryAssistVerifierCase, + zk: bool, +) where + T: Transcript, +{ + case.assist_proof.claims.stage1.public.input = + checked_input_public_claims_for_fixture::(case, zk); + bind_dory_reduce_scalar_fold_fixture(case); + bind_public_input_copy_fixture(case); +} + +fn bind_dory_reduce_scalar_fold_fixture(case: &mut DoryAssistVerifierCase) { + let point_len = case.point.len(); + let input_claims = &case.assist_proof.claims.stage1.public.input; + let s1_fold_factor = input_claims.transcript_scalars + [transcript_scalars::dory_reduce_s1_fold_factor(point_len, 0)]; + let s2_fold_factor = input_claims.transcript_scalars + [transcript_scalars::dory_reduce_s2_fold_factor(point_len, 0)]; + case.assist_proof + .claims + .stage1 + .dory_reduce + .scalar_fold + .s1_fold_factor = s1_fold_factor; + case.assist_proof + .claims + .stage1 + .dory_reduce + .scalar_fold + .s2_fold_factor = s2_fold_factor; +} + +fn bind_zero_dory_reduce_transition_fixture(assist_proof: &mut DoryAssistProof) { + assist_proof.claims.stage1.dory_reduce.transitions.clear(); + assist_proof.claims.stage1.dory_reduce.state_chain.clear(); + assist_proof.claims.stage1.dory_reduce.boundary.clear(); + let protocol = protocol_claims::(assist_proof.dimensions); + for relation_id in [ + DoryAssistRelationId::DoryReduceGtTransition, + DoryAssistRelationId::DoryReduceG1Transition, + DoryAssistRelationId::DoryReduceG2Transition, + ] { + let relation = protocol + .relation(relation_id) + .expect("Dory-reduce transition relation is in the protocol catalog"); + assist_proof.claims.stage1.dory_reduce.transitions.extend( + relation + .required_openings() + .into_iter() + .map(|id| DoryAssistOpeningClaim { + id, + value: Fq::default(), + }), + ); + } + if assist_proof.dimensions.dory_reduce.reduce_rounds() > 1 { + for relation_id in [ + DoryAssistRelationId::DoryReduceStateChain, + DoryAssistRelationId::DoryReduceBoundary, + ] { + let relation = protocol + .relation(relation_id) + .expect("Dory-reduce multi-round relation is in the protocol catalog"); + let claims = + relation + .required_openings() + .into_iter() + .map(|id| DoryAssistOpeningClaim { + id, + value: Fq::default(), + }); + match relation_id { + DoryAssistRelationId::DoryReduceStateChain => { + assist_proof + .claims + .stage1 + .dory_reduce + .state_chain + .extend(claims); + } + DoryAssistRelationId::DoryReduceBoundary => { + assist_proof + .claims + .stage1 + .dory_reduce + .boundary + .extend(claims); + } + _ => unreachable!("only multi-round Dory-reduce relations are handled here"), + } + } + } +} + +fn bind_public_input_copy_fixture(case: &mut DoryAssistVerifierCase) { + let vmv_c0 = case + .assist_proof + .claims + .stage1 + .public + .input + .dory_proof_artifacts[DORY_VMV_C_START]; + case.assist_proof + .claims + .stage1 + .gt_exponentiation + .accumulator = vmv_c0; + case.assist_proof + .claims + .stage1 + .gt_exponentiation_shift + .accumulator = vmv_c0; + case.assist_proof + .claims + .stage1 + .gt_exponentiation_boundary + .accumulator = vmv_c0; + case.assist_proof.claims.stage1.public.gt_shift_eq_kernel = Fq::default(); + case.assist_proof + .claims + .stage1 + .public + .gt_exponentiation_boundary + .initial_value = vmv_c0; + case.assist_proof + .claims + .stage1 + .miller_loop + .line_evaluation + .g1_point_x = case + .assist_proof + .claims + .stage1 + .public + .input + .dory_proof_artifacts[DORY_VMV_E1_START]; + case.assist_proof + .claims + .stage1 + .miller_loop + .line_evaluation + .g1_point_y = case + .assist_proof + .claims + .stage1 + .public + .input + .dory_proof_artifacts[DORY_VMV_E1_START + 1]; +} + +fn bind_native_public_output_fixture(case: &mut DoryAssistVerifierCase, zk: bool) { + bind_native_public_output_fixture_with_transcript::>(case, zk); +} + +fn bind_native_public_output_fixture_with_transcript(case: &mut DoryAssistVerifierCase, zk: bool) +where + T: Transcript, +{ + if zk { + bind_zk_pre_final_output_fixture::(case); + } else { + bind_transparent_pre_final_output_fixture::(case); + } + + let output_coefficients = case + .assist_proof + .public_outputs + .pre_final_exponentiation_coefficients(); + case.assist_proof + .claims + .stage1 + .public + .miller_loop + .bind_pre_final_exponentiation(&case.assist_proof.public_outputs); + case.assist_proof + .claims + .stage1 + .public + .miller_loop + .boundary_initial_value = output_coefficients; + case.assist_proof + .claims + .stage1 + .miller_loop + .accumulator + .accumulator = output_coefficients; + case.assist_proof + .claims + .stage1 + .miller_loop + .accumulator + .shifted_accumulator = output_coefficients; + case.assist_proof + .claims + .stage1 + .miller_loop + .boundary + .accumulator = output_coefficients; + case.assist_proof + .claims + .stage1 + .miller_loop + .boundary + .shifted_accumulator = output_coefficients; + + let square_row = &mut case.assist_proof.claims.stage1.gt_multiplication.rows + [composition::ACCUMULATOR_SQUARE_GT_ROW]; + square_row.left = output_coefficients; + square_row.right = output_coefficients; + square_row.output = output_coefficients; + + let mul_row = &mut case.assist_proof.claims.stage1.gt_multiplication.rows + [composition::ACCUMULATOR_MUL_GT_ROW]; + mul_row.left = output_coefficients; + mul_row.output = output_coefficients; + + case.assist_proof + .claims + .stage1 + .public + .miller_loop + .accumulator_shift_eq_kernel = accumulator_zero_sumcheck_kernel::(case, zk); + bind_dory_reduce_transition_copy_fixture::(case, zk); + populate_valid_hyrax_opening(&mut case.assist_proof); +} + +fn bind_transparent_pre_final_output_fixture(case: &mut DoryAssistVerifierCase) +where + T: Transcript, +{ + let transcript = T::new(b"dory-assist-oracle"); + let scalars = case + .pcs_proof + .verifier_transcript_scalars(&transcript, &case.point); + let statement = ClearOpeningStatement { + setup: &case.verifier_setup, + pcs_proof: &case.pcs_proof, + commitment: &case.commitment, + point: &case.point, + eval: case.eval, + }; + case.assist_proof.claims.stage1.public.native_final.bind( + transparent_native_final_input_claims(&statement, &scalars) + .expect("transparent native-final fixture is well shaped"), + ); + case.assist_proof.public_outputs.pre_final_exponentiation = + transparent_replayed_final_pairing_check(&statement, &scalars) + .expect("transparent final fixture is well shaped") + .pre_final_exponentiation(); +} + +fn bind_zk_pre_final_output_fixture(case: &mut DoryAssistVerifierCase) +where + T: Transcript, +{ + let transcript = T::new(b"dory-assist-oracle"); + let scalars = case + .pcs_proof + .verifier_transcript_scalars(&transcript, &case.point); + let statement = ZkOpeningStatement { + setup: &case.verifier_setup, + pcs_proof: &case.pcs_proof, + commitment: &case.commitment, + point: &case.point, + }; + case.assist_proof.claims.stage1.public.native_final.bind( + zk_native_final_input_claims(&statement, &scalars) + .expect("ZK native-final fixture is well shaped"), + ); + case.assist_proof.public_outputs.pre_final_exponentiation = + zk_replayed_final_pairing_check(&statement, &scalars) + .expect("ZK final fixture is well shaped") + .pre_final_exponentiation(); +} + +fn bind_dory_reduce_transition_copy_fixture(case: &mut DoryAssistVerifierCase, zk: bool) +where + T: Transcript, +{ + for constraint in dory_reduce::initial_state_copy_constraints() { + bind_dory_reduce_copy_target(case, constraint); + } + if case.assist_proof.dimensions.dory_reduce.reduce_rounds() == 1 { + for constraint in dory_reduce_transition_copy_constraints(case.assist_proof.dimensions) { + bind_dory_reduce_copy_target(case, constraint); + } + } + bind_dory_reduce_public_fold_fixture::( + case, + zk, + DoryAssistRelationId::DoryReduceGtTransition, + ); + rebalance_dory_reduce_transition_relation::( + case, + zk, + DoryAssistRelationId::DoryReduceGtTransition, + ); + bind_dory_reduce_public_fold_fixture::( + case, + zk, + DoryAssistRelationId::DoryReduceG1Transition, + ); + rebalance_dory_reduce_transition_relation::( + case, + zk, + DoryAssistRelationId::DoryReduceG1Transition, + ); + bind_dory_reduce_public_fold_fixture::( + case, + zk, + DoryAssistRelationId::DoryReduceG2Transition, + ); + rebalance_dory_reduce_transition_relation::( + case, + zk, + DoryAssistRelationId::DoryReduceG2Transition, + ); + bind_dory_reduce_public_fold_fixture::(case, zk, DoryAssistRelationId::DoryReduceScalarFold); + rebalance_dory_reduce_scalar_fold_relation::(case, zk); + if case.assist_proof.dimensions.dory_reduce.reduce_rounds() > 1 { + bind_dory_reduce_boundary_fixture(case); + } +} + +fn dory_reduce_transition_copy_constraints( + dimensions: jolt_claims::protocols::dory_assist::DoryAssistDimensions, +) -> Vec { + dory_reduce::proof_artifact_copy_constraints(0) + .into_iter() + .chain(dory_reduce::round_setup_artifact_copy_constraints( + dimensions.dory_reduce.reduce_rounds(), + 0, + )) + .chain(dory_reduce::transition_transcript_scalar_copy_constraints( + dimensions.dory_reduce.point_len(), + 0, + )) + .collect() +} + +fn bind_dory_reduce_copy_target( + case: &mut DoryAssistVerifierCase, + constraint: DoryAssistCopyConstraint, +) { + let value = match constraint.source { + DoryAssistValueRef::Public { id, .. } => case + .assist_proof + .claims + .stage1 + .public + .claim(&id) + .expect("fixture public claim exists"), + DoryAssistValueRef::Constant(value) => Fq::from_u64(value as u64), + DoryAssistValueRef::Witness { .. } | DoryAssistValueRef::Challenge(_) => { + panic!("Dory-reduce fixture copy source must be public or constant") + } + }; + let opening = constraint + .target + .witness_opening() + .expect("Dory-reduce transition fixture copy target must be witness"); + set_dory_reduce_opening(&mut case.assist_proof, opening, value); +} + +fn bind_dory_reduce_public_fold_fixture( + case: &mut DoryAssistVerifierCase, + zk: bool, + relation_id: DoryAssistRelationId, +) where + T: Transcript, +{ + let context = stage1_relation_context_for_fixture::(case, zk, relation_id); + let weights = EqPolynomial::new(context.sumcheck_point).evaluations(); + for constraint in dory_reduce::public_fold_constraints(case.assist_proof.dimensions.dory_reduce) + { + let opening = constraint + .target + .witness_opening() + .expect("Dory-reduce public fold target is a witness opening"); + if opening_relation(opening) != relation_id { + continue; + } + assert!( + constraint.sources.len() <= weights.len(), + "public fold sources fit the relation point domain" + ); + let value = + constraint + .sources + .iter() + .zip(&weights) + .fold(Fq::default(), |acc, (id, weight)| { + let public = case + .assist_proof + .claims + .stage1 + .public_claim(id) + .expect("fixture public-fold source exists"); + acc + public * *weight + }); + set_dory_reduce_opening(&mut case.assist_proof, opening, value); + } +} + +fn bind_dory_reduce_boundary_fixture(case: &mut DoryAssistVerifierCase) { + for term in dory_reduce::initial_boundary_terms() + .into_iter() + .chain(dory_reduce::final_boundary_terms()) + { + let value = match term.value { + dory_reduce::DoryReduceBoundaryValue::ConstantOne => Fq::from_u64(1), + dory_reduce::DoryReduceBoundaryValue::Public(id) => case + .assist_proof + .claims + .stage1 + .public_claim(&id) + .expect("fixture Dory-reduce boundary public claim exists"), + }; + set_dory_reduce_opening(&mut case.assist_proof, term.opening, value); + } +} + +fn rebalance_dory_reduce_transition_relation( + case: &mut DoryAssistVerifierCase, + zk: bool, + relation_id: DoryAssistRelationId, +) where + T: Transcript, +{ + let protocol = protocol_claims::(case.assist_proof.dimensions); + let relation = protocol + .relation(relation_id) + .expect("Dory-reduce transition relation is in the protocol catalog"); + let context = stage1_relation_context_for_fixture::(case, zk, relation_id); + let target = dory_reduce_transition_target_opening(relation_id); + let input = relation + .input + .expression() + .try_evaluate( + |id| { + case.assist_proof + .claims + .stage1 + .opening_claim(id) + .ok_or("missing opening") + }, + |id| resolve_fixture_challenge(&context.relation_challenges, id), + |id| { + case.assist_proof + .claims + .stage1 + .public_claim(id) + .ok_or("missing public") + }, + ) + .expect("fixture Dory-reduce transition input evaluates"); + let output = relation + .output + .expression() + .try_evaluate( + |id| { + case.assist_proof + .claims + .stage1 + .opening_claim(id) + .ok_or("missing opening") + }, + |id| resolve_fixture_challenge(&context.relation_challenges, id), + |id| { + case.assist_proof + .claims + .stage1 + .public_claim(id) + .ok_or("missing public") + }, + ) + .expect("fixture Dory-reduce transition output evaluates"); + let factor = sumcheck_linear_factor(&context.sumcheck_point); + let delta = (output - input * factor) + * factor + .inverse() + .expect("fixture Dory-reduce sumcheck factor is nonzero"); + let adjusted = get_dory_reduce_opening(&case.assist_proof, target) + delta; + set_dory_reduce_opening(&mut case.assist_proof, target, adjusted); +} + +fn rebalance_dory_reduce_transition_relations(case: &mut DoryAssistVerifierCase, zk: bool) { + rebalance_dory_reduce_transition_relation::>( + case, + zk, + DoryAssistRelationId::DoryReduceGtTransition, + ); + rebalance_dory_reduce_transition_relation::>( + case, + zk, + DoryAssistRelationId::DoryReduceG1Transition, + ); + rebalance_dory_reduce_transition_relation::>( + case, + zk, + DoryAssistRelationId::DoryReduceG2Transition, + ); + rebalance_dory_reduce_scalar_fold_relation::>(case, zk); +} + +fn rebalance_dory_reduce_scalar_fold_relation(case: &mut DoryAssistVerifierCase, zk: bool) +where + T: Transcript, +{ + let protocol = protocol_claims::(case.assist_proof.dimensions); + let relation = protocol + .relation(DoryAssistRelationId::DoryReduceScalarFold) + .expect("Dory-reduce scalar-fold relation is in the protocol catalog"); + let context = stage1_relation_context_for_fixture::( + case, + zk, + DoryAssistRelationId::DoryReduceScalarFold, + ); + let input = relation + .input + .expression() + .try_evaluate( + |id| { + case.assist_proof + .claims + .stage1 + .opening_claim(id) + .ok_or("missing opening") + }, + |id| resolve_fixture_challenge(&context.relation_challenges, id), + |id| { + case.assist_proof + .claims + .stage1 + .public_claim(id) + .ok_or("missing public") + }, + ) + .expect("fixture Dory-reduce scalar-fold input evaluates"); + let output = relation + .output + .expression() + .try_evaluate( + |id| { + case.assist_proof + .claims + .stage1 + .opening_claim(id) + .ok_or("missing opening") + }, + |id| resolve_fixture_challenge(&context.relation_challenges, id), + |id| { + case.assist_proof + .claims + .stage1 + .public_claim(id) + .ok_or("missing public") + }, + ) + .expect("fixture Dory-reduce scalar-fold output evaluates"); + let factor = sumcheck_linear_factor(&context.sumcheck_point); + let target = dory_reduce::s1_next_accumulator_opening(); + let delta = (output - input * factor) + * factor + .inverse() + .expect("fixture Dory-reduce scalar-fold sumcheck factor is nonzero"); + let adjusted = get_dory_reduce_opening(&case.assist_proof, target) + delta; + set_dory_reduce_opening(&mut case.assist_proof, target, adjusted); +} + +fn dory_reduce_transition_target_opening(relation: DoryAssistRelationId) -> DoryAssistOpeningId { + let polynomial = match relation { + DoryAssistRelationId::DoryReduceGtTransition => DoryReducePolynomial::NextC(0), + DoryAssistRelationId::DoryReduceG1Transition => DoryReducePolynomial::NextE1X, + DoryAssistRelationId::DoryReduceG2Transition => DoryReducePolynomial::NextE2X0, + _ => panic!("not a Dory-reduce transition relation"), + }; + dory_reduce_opening(relation, polynomial) +} + +fn dory_reduce_opening( + relation: DoryAssistRelationId, + polynomial: DoryReducePolynomial, +) -> DoryAssistOpeningId { + DoryAssistOpeningId::virtual_polynomial( + DoryAssistVirtualPolynomial::DoryReduce(polynomial), + relation, + ) +} + +fn set_dory_reduce_transition_opening( + assist_proof: &mut DoryAssistProof, + opening: DoryAssistOpeningId, + value: Fq, +) { + let claim = assist_proof + .claims + .stage1 + .dory_reduce + .transitions + .iter_mut() + .find(|claim| claim.id == opening) + .expect("fixture contains Dory-reduce transition opening"); + claim.value = value; +} + +fn set_dory_reduce_opening_claim( + claims: &mut [DoryAssistOpeningClaim], + opening: DoryAssistOpeningId, + value: Fq, +) { + let claim = claims + .iter_mut() + .find(|claim| claim.id == opening) + .expect("fixture contains Dory-reduce relation opening"); + claim.value = value; +} + +fn set_dory_reduce_opening( + assist_proof: &mut DoryAssistProof, + opening: DoryAssistOpeningId, + value: Fq, +) { + if opening == dory_reduce::s1_accumulator_opening() { + assist_proof + .claims + .stage1 + .dory_reduce + .scalar_fold + .s1_accumulator = value; + } else if opening == dory_reduce::s1_next_accumulator_opening() { + assist_proof + .claims + .stage1 + .dory_reduce + .scalar_fold + .s1_next_accumulator = value; + } else if opening == dory_reduce::s1_fold_factor_opening() { + assist_proof + .claims + .stage1 + .dory_reduce + .scalar_fold + .s1_fold_factor = value; + } else if opening == dory_reduce::s2_accumulator_opening() { + assist_proof + .claims + .stage1 + .dory_reduce + .scalar_fold + .s2_accumulator = value; + } else if opening == dory_reduce::s2_next_accumulator_opening() { + assist_proof + .claims + .stage1 + .dory_reduce + .scalar_fold + .s2_next_accumulator = value; + } else if opening == dory_reduce::s2_fold_factor_opening() { + assist_proof + .claims + .stage1 + .dory_reduce + .scalar_fold + .s2_fold_factor = value; + } else { + match opening_relation(opening) { + DoryAssistRelationId::DoryReduceGtTransition + | DoryAssistRelationId::DoryReduceG1Transition + | DoryAssistRelationId::DoryReduceG2Transition => { + set_dory_reduce_transition_opening(assist_proof, opening, value); + } + DoryAssistRelationId::DoryReduceStateChain => set_dory_reduce_opening_claim( + &mut assist_proof.claims.stage1.dory_reduce.state_chain, + opening, + value, + ), + DoryAssistRelationId::DoryReduceBoundary => set_dory_reduce_opening_claim( + &mut assist_proof.claims.stage1.dory_reduce.boundary, + opening, + value, + ), + relation => panic!("fixture cannot set non-Dory-reduce opening {relation:?}"), + } + } +} + +fn get_dory_reduce_opening(assist_proof: &DoryAssistProof, opening: DoryAssistOpeningId) -> Fq { + assist_proof + .claims + .stage1 + .opening_claim(&opening) + .expect("fixture Dory-reduce opening exists") +} + +fn opening_relation(opening: DoryAssistOpeningId) -> DoryAssistRelationId { + let DoryAssistOpeningId::Polynomial { relation, .. } = opening; + relation +} + +fn add_to_dory_reduce_transition_opening( + assist_proof: &mut DoryAssistProof, + opening: DoryAssistOpeningId, + delta: Fq, +) { + let claim = assist_proof + .claims + .stage1 + .dory_reduce + .transitions + .iter_mut() + .find(|claim| claim.id == opening) + .expect("fixture contains Dory-reduce transition opening"); + claim.value += delta; +} + +struct Stage1RelationContextForFixture { + relation_challenges: Vec<(DoryAssistChallengeId, Fq)>, + sumcheck_point: Vec, +} + +fn stage1_relation_context_for_fixture( + case: &DoryAssistVerifierCase, + zk: bool, + target: DoryAssistRelationId, +) -> Stage1RelationContextForFixture +where + T: Transcript, +{ + let mut transcript = T::new(b"dory-assist-oracle"); + let _ = absorb_checked_inputs_for_fixture(case, zk, &mut transcript); + let _ = squeeze_checked_input_digest_for_fixture(&mut transcript); + absorb_stage1_preamble_for_fixture( + if zk { &b"zk"[..] } else { &b"clear"[..] }, + case.assist_proof.stages.stage1.relation_count(), + &mut transcript, + ); + + let protocol = protocol_claims::(case.assist_proof.dimensions); + for relation in &case.assist_proof.stages.stage1.relations { + let relation_claims = protocol + .relation(relation.id) + .expect("stage 1 relation belongs to Dory-assist protocol"); + absorb_stage1_relation_for_fixture(relation.id, &relation.sumcheck, &mut transcript); + let relation_challenges = relation_claims + .required_challenges() + .into_iter() + .map(|id| (id, squeeze_fq_for_fixture(&mut transcript))) + .collect::>(); + + if relation.id == target { + let mut sumcheck_point = Vec::with_capacity(relation.sumcheck.rounds); + for round_proof in &relation.sumcheck_proof.round_polynomials { + absorb_sumcheck_round_for_fixture(round_proof, &mut transcript); + sumcheck_point.push(squeeze_fq_for_fixture(&mut transcript)); + } + return Stage1RelationContextForFixture { + relation_challenges, + sumcheck_point, + }; + } + + for round_proof in &relation.sumcheck_proof.round_polynomials { + absorb_sumcheck_round_for_fixture(round_proof, &mut transcript); + let _ = squeeze_fq_for_fixture(&mut transcript); + } + for id in relation_claims.required_openings() { + let value = case + .assist_proof + .claims + .stage1 + .opening_claim(&id) + .expect("fixture has canonical opening claim"); + transcript.append_labeled(b"opening_claim", &value); + } + } + + panic!("target relation {target:?} is absent from the canonical Stage 1 catalog"); +} + +fn sumcheck_linear_factor(point: &[Fq]) -> Fq { + point + .iter() + .copied() + .fold(Fq::from_u64(1), |acc, challenge| acc * challenge) +} + +fn accumulator_zero_sumcheck_kernel(case: &DoryAssistVerifierCase, zk: bool) -> Fq +where + T: Transcript, +{ + let mut transcript = T::new(b"dory-assist-oracle"); + let _ = absorb_checked_inputs_for_fixture(case, zk, &mut transcript); + let _ = squeeze_checked_input_digest_for_fixture(&mut transcript); + absorb_stage1_preamble_for_fixture( + if zk { &b"zk"[..] } else { &b"clear"[..] }, + case.assist_proof.stages.stage1.relation_count(), + &mut transcript, + ); + + let protocol = protocol_claims::(case.assist_proof.dimensions); + for relation in &case.assist_proof.stages.stage1.relations { + let relation_claims = protocol + .relation(relation.id) + .expect("stage 1 relation belongs to Dory-assist protocol"); + absorb_stage1_relation_for_fixture(relation.id, &relation.sumcheck, &mut transcript); + let relation_challenges = relation_claims + .required_challenges() + .into_iter() + .map(|id| (id, squeeze_fq_for_fixture(&mut transcript))) + .collect::>(); + + if relation.id == DoryAssistRelationId::MillerLoopAccumulator { + let input_claim = relation_claims + .input + .expression() + .try_evaluate( + |id| { + case.assist_proof + .claims + .stage1 + .opening_claim(id) + .ok_or("missing opening") + }, + |id| resolve_fixture_challenge(&relation_challenges, id), + |id| { + case.assist_proof + .claims + .stage1 + .public_claim(id) + .ok_or("missing public") + }, + ) + .expect("fixture accumulator input evaluates"); + let final_claim = relation.sumcheck_proof.round_polynomials.iter().fold( + input_claim, + |running_sum, round_proof| { + absorb_sumcheck_round_for_fixture(round_proof, &mut transcript); + let challenge = squeeze_fq_for_fixture(&mut transcript); + round_proof.evaluate_with_hint(running_sum, challenge) + }, + ); + return final_claim + * input_claim + .inverse() + .expect("native-output fixture has nonzero accumulator claim"); + } + + for round_proof in &relation.sumcheck_proof.round_polynomials { + absorb_sumcheck_round_for_fixture(round_proof, &mut transcript); + let _ = squeeze_fq_for_fixture(&mut transcript); + } + for id in relation_claims.required_openings() { + let value = case + .assist_proof + .claims + .stage1 + .opening_claim(&id) + .expect("fixture has canonical opening claim"); + transcript.append_labeled(b"opening_claim", &value); + } + } + + panic!("canonical Stage 1 relation catalog has no Miller-loop accumulator relation"); +} + +fn stage1_zero_sumcheck_final_claim( + case: &DoryAssistVerifierCase, + zk: bool, + target: DoryAssistRelationId, +) -> Fq { + stage1_zero_sumcheck_final_claim_with_transcript::>(case, zk, target) +} + +fn stage1_zero_sumcheck_final_claim_with_transcript( + case: &DoryAssistVerifierCase, + zk: bool, + target: DoryAssistRelationId, +) -> Fq +where + T: Transcript, +{ + let mut transcript = T::new(b"dory-assist-oracle"); + let _ = absorb_checked_inputs_for_fixture(case, zk, &mut transcript); + let _ = squeeze_checked_input_digest_for_fixture(&mut transcript); + absorb_stage1_preamble_for_fixture( + if zk { &b"zk"[..] } else { &b"clear"[..] }, + case.assist_proof.stages.stage1.relation_count(), + &mut transcript, + ); + + let protocol = protocol_claims::(case.assist_proof.dimensions); + for relation in &case.assist_proof.stages.stage1.relations { + let relation_claims = protocol + .relation(relation.id) + .expect("stage 1 relation belongs to Dory-assist protocol"); + absorb_stage1_relation_for_fixture(relation.id, &relation.sumcheck, &mut transcript); + let relation_challenges = relation_claims + .required_challenges() + .into_iter() + .map(|id| (id, squeeze_fq_for_fixture(&mut transcript))) + .collect::>(); + + let input_claim = relation_claims + .input + .expression() + .try_evaluate( + |id| { + case.assist_proof + .claims + .stage1 + .opening_claim(id) + .ok_or("missing opening") + }, + |id| resolve_fixture_challenge(&relation_challenges, id), + |id| { + case.assist_proof + .claims + .stage1 + .public_claim(id) + .ok_or("missing public") + }, + ) + .expect("fixture input evaluates"); + let final_claim = relation.sumcheck_proof.round_polynomials.iter().fold( + input_claim, + |running_sum, round_proof| { + absorb_sumcheck_round_for_fixture(round_proof, &mut transcript); + let challenge = squeeze_fq_for_fixture(&mut transcript); + round_proof.evaluate_with_hint(running_sum, challenge) + }, + ); + + if relation.id == target { + return final_claim; + } + + for id in relation_claims.required_openings() { + let value = case + .assist_proof + .claims + .stage1 + .opening_claim(&id) + .expect("fixture has canonical opening claim"); + transcript.append_labeled(b"opening_claim", &value); + } + } + + panic!("target relation {target:?} is absent from the canonical Stage 1 catalog"); +} + +fn resolve_fixture_challenge( + challenges: &[(DoryAssistChallengeId, Fq)], + id: &DoryAssistChallengeId, +) -> Result { + challenges + .iter() + .find(|(candidate, _)| candidate == id) + .map(|(_, value)| *value) + .ok_or("missing challenge") +} + +fn absorb_checked_inputs_for_fixture( + case: &DoryAssistVerifierCase, + zk: bool, + transcript: &mut impl Transcript, +) -> DoryAssistInputPublicClaims { + let dory_verifier_scalars = + dory_verifier_transcript_scalar_claims_for_fixture(case, transcript); + let mut input_public_claims = DoryAssistInputPublicClaims::default(); + transcript.append(&Label(b"DoryAssist")); + transcript.append(&Label(b"checked_inputs")); + transcript.append(&Label(if zk { &b"zk"[..] } else { &b"clear"[..] })); + transcript.append(&Label(b"dory_assist_setup")); + transcript.append(&case.verifier_setup); + input_public_claims.verifier_setup_digest = + forked_fq_challenge_for_fixture(transcript, b"dory_assist_setup_digest"); + append_dory_verifier_setup_artifacts_for_fixture( + &mut input_public_claims.verifier_setup_artifacts, + &case.verifier_setup, + ); + + transcript.append(&Label(b"dory_assist_pcs_proof")); + transcript.append(&case.pcs_proof); + input_public_claims + .dory_proof_artifacts + .push(forked_fq_challenge_for_fixture( + transcript, + b"dory_assist_proof_digest", + )); + append_dory_proof_artifacts_for_fixture( + &mut input_public_claims.dory_proof_artifacts, + &case.pcs_proof, + ); + + transcript.append(&Label(b"dory_assist_commitment")); + transcript.append(&case.commitment); + input_public_claims + .jolt_commitments + .push(forked_fq_challenge_for_fixture( + transcript, + b"dory_assist_commitment_digest", + )); + input_public_claims + .jolt_commitments + .extend(gt_artifact_coefficients_for_fixture(&case.commitment.0)); + + transcript.append(&LabelWithCount( + b"dory_assist_point", + case.point.len() as u64, + )); + for point_coordinate in &case.point { + transcript.append(point_coordinate); + } + input_public_claims + .transcript_scalars + .extend(case.point.iter().copied().map(inject_fr_to_fq_for_fixture)); + input_public_claims + .transcript_scalars + .extend(dory_verifier_scalars); + + if !zk { + transcript.append(&Label(b"dory_assist_eval")); + transcript.append(&case.eval); + input_public_claims + .jolt_evaluation_claims + .push(inject_fr_to_fq_for_fixture(case.eval)); + input_public_claims + .dory_reduce_initial_e2 + .extend(g2_artifact_coordinates_for_fixture( + case.verifier_setup.artifacts().g2_0.scalar_mul(&case.eval), + )); + } else { + input_public_claims + .dory_reduce_initial_e2 + .extend(g2_artifact_coordinates_for_fixture( + case.pcs_proof.zk_artifacts().e2.unwrap_or_default(), + )); + } + + input_public_claims +} + +fn dory_verifier_transcript_scalar_claims_for_fixture( + case: &DoryAssistVerifierCase, + transcript: &T, +) -> Vec +where + T: Transcript, +{ + let scalars = case + .pcs_proof + .verifier_transcript_scalars(transcript, &case.point); + let mut claims = Vec::with_capacity( + 8 * scalars.reduce_rounds.len() + 4 + usize::from(scalars.scalar_product_sigma_c.is_some()), + ); + for round in scalars.reduce_rounds { + claims.push(inject_fr_to_fq_for_fixture(round.beta)); + claims.push(inject_fr_to_fq_for_fixture(round.beta_inverse)); + claims.push(inject_fr_to_fq_for_fixture(round.alpha)); + claims.push(inject_fr_to_fq_for_fixture(round.alpha_inverse)); + claims.push(inject_fr_to_fq_for_fixture(round.alpha_beta)); + claims.push(inject_fr_to_fq_for_fixture( + round.alpha_inverse_beta_inverse, + )); + claims.push(inject_fr_to_fq_for_fixture(round.s1_fold_factor)); + claims.push(inject_fr_to_fq_for_fixture(round.s2_fold_factor)); + } + claims.push(inject_fr_to_fq_for_fixture(scalars.gamma)); + claims.push(inject_fr_to_fq_for_fixture(scalars.gamma_inverse)); + if let Some(sigma_c) = scalars.scalar_product_sigma_c { + claims.push(inject_fr_to_fq_for_fixture(sigma_c)); + } + claims.push(inject_fr_to_fq_for_fixture(scalars.d)); + claims.push(inject_fr_to_fq_for_fixture(scalars.d_inverse)); + claims.push(inject_fr_to_fq_for_fixture(scalars.d_squared)); + claims +} + +fn append_dory_proof_artifacts_for_fixture(artifacts: &mut Vec, pcs_proof: &DoryProof) { + let vmv = pcs_proof.vmv_artifacts(); + artifacts.extend(gt_artifact_coefficients_for_fixture(&vmv.c)); + artifacts.extend(gt_artifact_coefficients_for_fixture(&vmv.d2)); + artifacts.extend(g1_artifact_coordinates_for_fixture(vmv.e1)); + + let zk = pcs_proof.zk_artifacts(); + artifacts.extend(match zk.e2 { + Some(e2) => g2_artifact_coordinates_for_fixture(e2), + None => identity_g2_artifact_coordinates_for_fixture(), + }); + artifacts.extend(match zk.y_com { + Some(y_com) => g1_artifact_coordinates_for_fixture(y_com), + None => identity_g1_artifact_coordinates_for_fixture(), + }); + if let Some(scalar_product) = pcs_proof.scalar_product_artifacts() { + artifacts.extend(gt_artifact_coefficients_for_fixture(&scalar_product.p1)); + artifacts.extend(gt_artifact_coefficients_for_fixture(&scalar_product.p2)); + artifacts.extend(gt_artifact_coefficients_for_fixture(&scalar_product.q)); + artifacts.extend(gt_artifact_coefficients_for_fixture(&scalar_product.r)); + artifacts.extend(g1_artifact_coordinates_for_fixture(scalar_product.e1)); + artifacts.extend(g2_artifact_coordinates_for_fixture(scalar_product.e2)); + artifacts.push(inject_fr_to_fq_for_fixture(scalar_product.r1)); + artifacts.push(inject_fr_to_fq_for_fixture(scalar_product.r2)); + artifacts.push(inject_fr_to_fq_for_fixture(scalar_product.r3)); + } else { + let identity_gt = Bn254GT::default(); + artifacts.extend(gt_artifact_coefficients_for_fixture(&identity_gt)); + artifacts.extend(gt_artifact_coefficients_for_fixture(&identity_gt)); + artifacts.extend(gt_artifact_coefficients_for_fixture(&identity_gt)); + artifacts.extend(gt_artifact_coefficients_for_fixture(&identity_gt)); + artifacts.extend(identity_g1_artifact_coordinates_for_fixture()); + artifacts.extend(identity_g2_artifact_coordinates_for_fixture()); + artifacts.extend([Fq::default(), Fq::default(), Fq::default()]); + } + + for round in pcs_proof.reduce_round_artifacts() { + artifacts.extend(gt_artifact_coefficients_for_fixture(&round.first.d1_left)); + artifacts.extend(gt_artifact_coefficients_for_fixture(&round.first.d1_right)); + artifacts.extend(gt_artifact_coefficients_for_fixture(&round.first.d2_left)); + artifacts.extend(gt_artifact_coefficients_for_fixture(&round.first.d2_right)); + artifacts.extend(g1_artifact_coordinates_for_fixture(round.first.e1_beta)); + artifacts.extend(g2_artifact_coordinates_for_fixture(round.first.e2_beta)); + + artifacts.extend(gt_artifact_coefficients_for_fixture(&round.second.c_plus)); + artifacts.extend(gt_artifact_coefficients_for_fixture(&round.second.c_minus)); + artifacts.extend(g1_artifact_coordinates_for_fixture(round.second.e1_plus)); + artifacts.extend(g1_artifact_coordinates_for_fixture(round.second.e1_minus)); + artifacts.extend(g2_artifact_coordinates_for_fixture(round.second.e2_plus)); + artifacts.extend(g2_artifact_coordinates_for_fixture(round.second.e2_minus)); + } + + let final_artifacts = pcs_proof.final_artifacts(); + artifacts.extend(g1_artifact_coordinates_for_fixture(final_artifacts.e1)); + artifacts.extend(g2_artifact_coordinates_for_fixture(final_artifacts.e2)); +} + +fn append_dory_verifier_setup_artifacts_for_fixture( + artifacts: &mut Vec, + setup: &DoryVerifierSetup, +) { + let setup_artifacts = setup.artifacts(); + for value in &setup_artifacts.chi { + artifacts.extend(gt_artifact_coefficients_for_fixture(value)); + } + for value in &setup_artifacts.delta_1l { + artifacts.extend(gt_artifact_coefficients_for_fixture(value)); + } + for value in &setup_artifacts.delta_1r { + artifacts.extend(gt_artifact_coefficients_for_fixture(value)); + } + for value in &setup_artifacts.delta_2l { + artifacts.extend(gt_artifact_coefficients_for_fixture(value)); + } + for value in &setup_artifacts.delta_2r { + artifacts.extend(gt_artifact_coefficients_for_fixture(value)); + } + artifacts.extend(g1_artifact_coordinates_for_fixture(setup_artifacts.g1_0)); + artifacts.extend(g2_artifact_coordinates_for_fixture(setup_artifacts.g2_0)); + artifacts.extend(g1_artifact_coordinates_for_fixture(setup_artifacts.h1)); + artifacts.extend(g2_artifact_coordinates_for_fixture(setup_artifacts.h2)); + artifacts.extend(gt_artifact_coefficients_for_fixture(&setup_artifacts.ht)); +} + +fn gt_artifact_coefficients_for_fixture(value: &Bn254GT) -> [Fq; 16] { + let mut coefficients = [Fq::default(); 16]; + coefficients[..Bn254GT::FQ12_COEFFICIENTS].copy_from_slice(&value.fq12_coefficients()); + coefficients +} + +fn g1_artifact_coordinates_for_fixture(value: Bn254G1) -> [Fq; 3] { + value.affine_coordinates_with_infinity() +} + +fn g2_artifact_coordinates_for_fixture(value: Bn254G2) -> [Fq; 5] { + value.affine_coordinates_with_infinity() +} + +fn identity_g1_artifact_coordinates_for_fixture() -> [Fq; 3] { + [Fq::default(), Fq::default(), Fq::from_u64(1)] +} + +fn identity_g2_artifact_coordinates_for_fixture() -> [Fq; 5] { + [ + Fq::default(), + Fq::default(), + Fq::default(), + Fq::default(), + Fq::from_u64(1), + ] +} + +fn checked_input_public_claims_for_fixture( + case: &DoryAssistVerifierCase, + zk: bool, +) -> DoryAssistInputPublicClaims +where + T: Transcript, +{ + let mut transcript = T::new(b"dory-assist-oracle"); + let mut input_public_claims = absorb_checked_inputs_for_fixture(case, zk, &mut transcript); + input_public_claims.checked_input_digest = + squeeze_checked_input_digest_for_fixture(&mut transcript); + input_public_claims +} + +fn squeeze_checked_input_digest_for_fixture( + transcript: &mut impl Transcript, +) -> Fq { + transcript.append(&Label(b"dory_assist_checked_input_digest")); + squeeze_fq_for_fixture(transcript) +} + +fn forked_fq_challenge_for_fixture(transcript: &T, label: &'static [u8]) -> Fq +where + T: Transcript, +{ + let mut fork = transcript.clone(); + fork.append(&Label(label)); + squeeze_fq_for_fixture(&mut fork) +} + +fn inject_fr_to_fq_for_fixture(value: Fr) -> Fq { + let mut bytes = [0_u8; Fr::NUM_BYTES]; + value.to_bytes_le(&mut bytes); + Fq::from_le_bytes_mod_order(&bytes) +} + +fn absorb_stage1_preamble_for_fixture( + mode_name: &'static [u8], + relation_count: u32, + transcript: &mut impl Transcript, +) { + transcript.append(&Label(b"dory_assist_stage1")); + transcript.append(&Label(mode_name)); + transcript.append(&Label(b"stage1_relations")); + transcript.append(&U64Word(relation_count as u64)); +} + +fn absorb_stage1_relation_for_fixture( + id: DoryAssistRelationId, + sumcheck: &jolt_claims::protocols::dory_assist::DoryAssistSumcheckSpec, + transcript: &mut impl Transcript, +) { + transcript.append(&Label(b"stage1_relation_id")); + transcript.append(&U64Word(relation_transcript_tag_for_fixture(id) as u64)); + transcript.append(&Label(b"stage1_sumcheck_domain")); + transcript.append(&U64Word(0)); + transcript.append(&Label(b"stage1_sumcheck_rounds")); + transcript.append(&U64Word(sumcheck.rounds as u64)); + transcript.append(&Label(b"stage1_sumcheck_degree")); + transcript.append(&U64Word(sumcheck.degree as u64)); +} + +fn absorb_sumcheck_round_for_fixture( + round_proof: &jolt_poly::CompressedPoly, + transcript: &mut impl Transcript, +) { + let coeffs = round_proof.coeffs_except_linear_term(); + transcript.append(&LabelWithCount( + SUMCHECK_ROUND_TRANSCRIPT_LABEL, + coeffs.len() as u64, + )); + for coeff in coeffs { + transcript.append(coeff); + } +} + +fn relation_transcript_tag_for_fixture(id: DoryAssistRelationId) -> usize { + CANONICAL_RELATION_ORDER + .iter() + .position(|candidate| *candidate == id) + .expect("stage 1 relation has a canonical transcript tag") +} + +fn squeeze_fq_for_fixture(transcript: &mut impl Transcript) -> Fq { + let value = transcript.challenge_scalar(); + let mut bytes = [0_u8; Fr::NUM_BYTES]; + value.to_bytes_le(&mut bytes); + Fq::from_le_bytes_mod_order(&bytes) +} + +fn populate_valid_hyrax_opening(assist_proof: &mut DoryAssistProof) { + let reduced_claims = reduced_opening_claims(assist_proof); + let poly_len = reduced_claims.len().next_power_of_two(); + let num_vars = poly_len.trailing_zeros() as usize; + let row_vars = num_vars / 2; + let col_vars = num_vars - row_vars; + let dimensions = + HyraxDimensions::new(num_vars, row_vars, col_vars).expect("valid Hyrax dimensions"); + let hyrax_setup = derive_hyrax_prover_setup(dimensions).expect("seed-derived Hyrax setup"); + let mut evaluations = vec![Fq::default(); poly_len]; + for (slot, claim) in evaluations.iter_mut().zip(&reduced_claims) { + *slot = claim.value; + } + let packed_poly = Polynomial::::from(evaluations); + let packed_point = (0..num_vars) + .map(|index| Fq::from_u64(13 + 6 * index as u64)) + .collect::>(); + let packed_eval = packed_poly.evaluate(&packed_point); + let (dense_commitment, hint) = DoryAssistHyrax::commit(&packed_poly, &hyrax_setup); + let mut transcript = Blake2bTranscript::new(b"dory-assist-hyrax-fixture"); + let opening_proof = DoryAssistHyrax::open( + &packed_poly, + &packed_point, + packed_eval, + &hyrax_setup, + Some(hint), + &mut transcript, + ); + + assist_proof.stages.stage3.packed_eval = packed_eval; + assist_proof.stages.stage3.reduced_openings = + reduced_claims.iter().map(|claim| claim.id).collect(); + assist_proof.claims.opening.packed_point = packed_point; + assist_proof.claims.opening.packed_eval = packed_eval; + assist_proof.opening_proof = opening_proof; + assist_proof.dense_commitment = dense_commitment; +} + +fn reduced_opening_claims(assist_proof: &DoryAssistProof) -> Vec { + let protocol = protocol_claims::(assist_proof.dimensions); + let mut reduced_claims = Vec::new(); + for relation in &assist_proof.stages.stage1.relations { + let relation_claims = protocol + .relation(relation.id) + .expect("stage 1 relation belongs to Dory-assist protocol"); + for id in relation_claims.required_openings() { + if reduced_claims + .iter() + .any(|claim: &DoryAssistOpeningClaim| claim.id == id) + { + continue; + } + let value = assist_proof + .claims + .stage1 + .opening_claim(&id) + .expect("stage 1 claim value exists for canonical opening"); + reduced_claims.push(DoryAssistOpeningClaim { id, value }); + } + } + reduced_claims +}