diff --git a/Cargo.lock b/Cargo.lock index 45abb1bc65..ba6fae8a82 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6081,6 +6081,7 @@ version = "6.1.0" dependencies = [ "serde", "slop-algebra", + "slop-koala-bear", "thiserror 1.0.69", ] @@ -6163,10 +6164,12 @@ name = "slop-challenger" version = "6.1.0" dependencies = [ "futures", + "num-bigint 0.4.6", "p3-challenger", "serde", "slop-algebra", "slop-symmetric", + "thiserror 1.0.69", ] [[package]] @@ -6501,6 +6504,7 @@ dependencies = [ "derive-where", "futures", "itertools 0.14.0", + "num-bigint 0.4.6", "rand 0.8.5", "rayon", "serde", diff --git a/crates/hypercube/src/logup_gkr/prover.rs b/crates/hypercube/src/logup_gkr/prover.rs index 6c7aad75be..cc8f6fa077 100644 --- a/crates/hypercube/src/logup_gkr/prover.rs +++ b/crates/hypercube/src/logup_gkr/prover.rs @@ -149,8 +149,12 @@ impl> GkrProverImpl { let host_numerator = numerator.to_host().unwrap(); let host_denominator = denominator.to_host().unwrap(); - challenger.observe_variable_length_extension_slice(host_numerator.guts().as_slice()); - challenger.observe_variable_length_extension_slice(host_denominator.guts().as_slice()); + challenger + .observe_variable_length_extension_slice(host_numerator.guts().as_slice()) + .unwrap(); + challenger + .observe_variable_length_extension_slice(host_denominator.guts().as_slice()) + .unwrap(); let output_host = LogUpGkrOutput { numerator: host_numerator, denominator: host_denominator }; @@ -201,9 +205,11 @@ impl> GkrProverImpl { }; // Observe the openings. if let Some(prep_eval) = openings.preprocessed_trace_evaluations.as_ref() { - challenger.observe_variable_length_extension_slice(prep_eval); + challenger.observe_variable_length_extension_slice(prep_eval).unwrap(); } - challenger.observe_variable_length_extension_slice(&openings.main_trace_evaluations); + challenger + .observe_variable_length_extension_slice(&openings.main_trace_evaluations) + .unwrap(); chip_evaluations.insert(name.to_string(), openings); } diff --git a/crates/hypercube/src/logup_gkr/verifier.rs b/crates/hypercube/src/logup_gkr/verifier.rs index 428cf2acfd..cbf9c3e617 100644 --- a/crates/hypercube/src/logup_gkr/verifier.rs +++ b/crates/hypercube/src/logup_gkr/verifier.rs @@ -7,6 +7,7 @@ use itertools::Itertools; use slop_air::BaseAir; use slop_algebra::AbstractField; use slop_challenger::GrindingChallenger; +use slop_challenger::USizeOutOfFieldBounds; use slop_challenger::{CanObserve, FieldChallenger, IopCtx, VariableLengthChallenger}; use slop_multilinear::{ full_geq, partial_lagrange_blocking, Mle, MleEval, MultilinearPcsChallenger, Point, @@ -63,6 +64,9 @@ pub enum LogupGkrVerificationError { /// The public values verification failed. #[error("public values verification failed")] InvalidPublicValues, + /// A variable length slice observation can produce this error. + #[error("slice length out of field bounds")] + SliceLengthOutOfFieldBounds(#[from] USizeOutOfFieldBounds), } /// Verifier for `LogUp` GKR. @@ -157,8 +161,8 @@ impl> LogUpGkrVerifier { } // Observe the output claims. - challenger.observe_variable_length_extension_slice(numerator.guts().as_slice()); - challenger.observe_variable_length_extension_slice(denominator.guts().as_slice()); + challenger.observe_variable_length_extension_slice(numerator.guts().as_slice())?; + challenger.observe_variable_length_extension_slice(denominator.guts().as_slice())?; if denominator.guts().as_slice().iter().any(slop_algebra::Field::is_zero) { return Err(LogupGkrVerificationError::ZeroDenominator); @@ -277,14 +281,14 @@ impl> LogUpGkrVerifier { { // Observe the opening if let Some(prep_eval) = openings.preprocessed_trace_evaluations.as_ref() { - challenger.observe_variable_length_extension_slice(prep_eval); + challenger.observe_variable_length_extension_slice(prep_eval)?; if prep_eval.evaluations().sizes() != [chip.air.preprocessed_width()] { return Err(LogupGkrVerificationError::InvalidShape); } } else if chip.air.preprocessed_width() != 0 { return Err(LogupGkrVerificationError::InvalidShape); } - challenger.observe_variable_length_extension_slice(&openings.main_trace_evaluations); + challenger.observe_variable_length_extension_slice(&openings.main_trace_evaluations)?; if openings.main_trace_evaluations.evaluations().sizes() != [chip.air.width()] { return Err(LogupGkrVerificationError::InvalidShape); } diff --git a/crates/hypercube/src/prover/shard.rs b/crates/hypercube/src/prover/shard.rs index 35e27a7023..ba4c79ad6b 100644 --- a/crates/hypercube/src/prover/shard.rs +++ b/crates/hypercube/src/prover/shard.rs @@ -622,8 +622,8 @@ impl, C: DefaultJaggedProver> let (preprocessed_evals, main_evals) = evals.split_at(air.preprocessed_width()); // Observe the openings - challenger.observe_variable_length_extension_slice(preprocessed_evals); - challenger.observe_variable_length_extension_slice(main_evals); + challenger.observe_variable_length_extension_slice(preprocessed_evals).unwrap(); + challenger.observe_variable_length_extension_slice(main_evals).unwrap(); let preprocessed = AirOpenedValues { local: preprocessed_evals.to_vec() }; diff --git a/crates/hypercube/src/verifier/shard.rs b/crates/hypercube/src/verifier/shard.rs index 74e44703fd..7f32eb8483 100644 --- a/crates/hypercube/src/verifier/shard.rs +++ b/crates/hypercube/src/verifier/shard.rs @@ -15,7 +15,9 @@ use std::{ use itertools::Itertools; use slop_air::{Air, BaseAir}; use slop_algebra::{AbstractField, PrimeField32, TwoAdicField}; -use slop_challenger::{CanObserve, FieldChallenger, IopCtx, VariableLengthChallenger}; +use slop_challenger::{ + CanObserve, FieldChallenger, IopCtx, USizeOutOfFieldBounds, VariableLengthChallenger, +}; use slop_commit::Rounds; use slop_jagged::{JaggedPcsVerifier, JaggedPcsVerifierError}; use slop_matrix::dense::RowMajorMatrixView; @@ -115,6 +117,9 @@ pub enum ShardVerifierError { /// The height is larger than `1 << max_log_row_count`. #[error("height is larger than maximum possible value")] HeightTooLarge, + /// Observing variable length slices can produce this error. + #[error("slice length out of field bounds")] + SliceLengthOutOfFieldBounds(#[from] USizeOutOfFieldBounds), } /// Derive the error type from the jagged config. @@ -425,8 +430,8 @@ where { let len = shard_chips.len(); challenger.observe(GC::F::from_canonical_usize(len)); for (_, opening) in opened_values.chips.iter() { - challenger.observe_variable_length_extension_slice(&opening.preprocessed.local); - challenger.observe_variable_length_extension_slice(&opening.main.local); + challenger.observe_variable_length_extension_slice(&opening.preprocessed.local)?; + challenger.observe_variable_length_extension_slice(&opening.main.local)?; } Ok(()) @@ -775,7 +780,7 @@ where /// Create a shard verifier from basefold parameters. #[must_use] pub fn from_config( - config: &WhirProofShape, + config: &WhirProofShape, max_log_row_count: usize, machine: Machine, num_expected_commitments: usize, diff --git a/crates/recursion/circuit/src/basefold/whir.rs b/crates/recursion/circuit/src/basefold/whir.rs index f8d06722c2..8bb4d8f6e9 100644 --- a/crates/recursion/circuit/src/basefold/whir.rs +++ b/crates/recursion/circuit/src/basefold/whir.rs @@ -26,7 +26,7 @@ use sp1_recursion_compiler::{ #[derive(Clone)] pub struct RecursiveWhirVerifier> { - config: WhirProofShape, + config: WhirProofShape, _config: PhantomData<(C, SC)>, } @@ -34,35 +34,30 @@ pub fn write_round_config_to_challenger< C: CircuitConfig, SC: SP1FieldConfigVariable, >( - round_param: RoundConfig, + round_param: &RoundConfig, challenger: &mut SC::FriChallengerVariable, builder: &mut Builder, ) { - let RoundConfig { + let &RoundConfig { folding_factor, - evaluation_domain_log_size, queries_pow_bits, - pow_bits, + ref pow_bits, num_queries, ood_samples, - log_inv_rate, - } = round_param.clone(); + .. + } = round_param; let folding_factor_felt: Felt<_> = builder.constant(::F::from_canonical_usize(folding_factor)); challenger.observe(builder, folding_factor_felt); - let evaluation_domain_log_size_felt: Felt<_> = - builder.constant(::F::from_canonical_usize(evaluation_domain_log_size)); - challenger.observe(builder, evaluation_domain_log_size_felt); - let queries_pow_bits_felt: Felt<_> = builder.constant(::F::from_canonical_usize(queries_pow_bits)); challenger.observe(builder, queries_pow_bits_felt); let pow_bits_felt: Vec> = pow_bits - .into_iter() - .map(|b| builder.constant(::F::from_canonical_usize(b))) + .iter() + .map(|b| builder.constant(::F::from_canonical_usize(*b))) .collect(); challenger.observe_variable_length_slice(builder, &pow_bits_felt); @@ -73,88 +68,70 @@ pub fn write_round_config_to_challenger< let ood_samples_felt: Felt<_> = builder.constant(::F::from_canonical_usize(ood_samples)); challenger.observe(builder, ood_samples_felt); - - let log_inv_rate_felt: Felt<_> = - builder.constant(::F::from_canonical_usize(log_inv_rate)); - challenger.observe(builder, log_inv_rate_felt); } fn write_whir_config_to_challenger< C: CircuitConfig, SC: SP1FieldConfigVariable, >( - config: WhirProofShape, + config: WhirProofShape, challenger: &mut SC::FriChallengerVariable, builder: &mut Builder, ) { - let WhirProofShape { - domain_generator, - starting_ood_samples, - starting_log_inv_rate, - starting_interleaved_log_height, - starting_domain_log_size, - starting_folding_pow_bits, - round_parameters, - final_poly_log_degree, - final_queries, - final_pow_bits, - final_folding_pow_bits, - } = config.clone(); - - let domain_generator_felt: Felt<_> = builder.constant(domain_generator); - challenger.observe(builder, domain_generator_felt); - let starting_ood_samples_felt: Felt<_> = - builder.constant(::F::from_canonical_usize(starting_ood_samples)); + builder.constant(::F::from_canonical_usize(config.starting_ood_samples())); challenger.observe(builder, starting_ood_samples_felt); let starting_log_inv_rate_felt: Felt<_> = - builder.constant(::F::from_canonical_usize(starting_log_inv_rate)); + builder.constant(::F::from_canonical_usize(config.starting_log_inv_rate())); challenger.observe(builder, starting_log_inv_rate_felt); - let starting_interleaved_log_height_felt: Felt<_> = - builder.constant(::F::from_canonical_usize(starting_interleaved_log_height)); + let starting_interleaved_log_height_felt: Felt<_> = builder.constant( + ::F::from_canonical_usize(config.starting_interleaved_log_height()), + ); challenger.observe(builder, starting_interleaved_log_height_felt); - let starting_domain_log_size_felt: Felt<_> = - builder.constant(::F::from_canonical_usize(starting_domain_log_size)); - challenger.observe(builder, starting_domain_log_size_felt); - - let starting_folding_pow_bits_felt: Vec> = starting_folding_pow_bits - .into_iter() - .map(|b| builder.constant(::F::from_canonical_usize(b))) + let starting_folding_pow_bits_felt: Vec> = config + .starting_folding_pow_bits() + .iter() + .map(|b| builder.constant(::F::from_canonical_usize(*b))) .collect(); challenger.observe_variable_length_slice(builder, &starting_folding_pow_bits_felt); - for round_param in round_parameters { + let len_felt: Felt<_> = + builder.constant(::F::from_canonical_usize(config.round_parameters().len())); + challenger.observe(builder, len_felt); + for round_param in config.round_parameters() { write_round_config_to_challenger::(round_param, challenger, builder); } - let final_poly_log_degree_felt: Felt<_> = - builder.constant(::F::from_canonical_usize(final_poly_log_degree)); - challenger.observe(builder, final_poly_log_degree_felt); - let final_queries_felt: Felt<_> = - builder.constant(::F::from_canonical_usize(final_queries)); + builder.constant(::F::from_canonical_usize(config.final_queries())); challenger.observe(builder, final_queries_felt); let final_pow_bits_felt: Felt<_> = - builder.constant(::F::from_canonical_usize(final_pow_bits)); + builder.constant(::F::from_canonical_usize(config.final_pow_bits())); challenger.observe(builder, final_pow_bits_felt); - let final_folding_pow_bits_felt: Vec> = final_folding_pow_bits - .into_iter() - .map(|b| builder.constant(::F::from_canonical_usize(b))) + let final_folding_pow_bits_felt: Vec> = config + .final_folding_pow_bits() + .iter() + .map(|b| builder.constant(::F::from_canonical_usize(*b))) .collect(); challenger.observe_variable_length_slice(builder, &final_folding_pow_bits_felt); } impl> RecursiveWhirVerifier { pub fn new( - config: WhirProofShape, + config: WhirProofShape, builder: &mut Builder, challenger: &mut SC::FriChallengerVariable, + num_expected_commitments: usize, ) -> Self { + let num_expected_commitments_felt: Felt<_> = + builder.constant(::F::from_canonical_usize(num_expected_commitments)); + challenger.observe(builder, num_expected_commitments_felt); + write_whir_config_to_challenger::(config.clone(), challenger, builder); Self { config, _config: PhantomData } } @@ -205,7 +182,7 @@ where pub _config: PhantomData, } -impl> RecursiveWhirVerifier { +impl> RecursiveWhirVerifier { pub(crate) fn observe_commitment( &self, builder: &mut Builder, @@ -224,11 +201,16 @@ impl> RecursiveWhirVerifier, challenger: &mut SC::FriChallengerVariable, - ) -> PointAndEval> { - let n_rounds = self.config.round_parameters.len(); + ) -> PointAndEval> +where { + let n_rounds = self.config.round_parameters().len(); + let num_variables_felt: Felt<_> = + builder.constant(SC::F::from_canonical_usize(num_variables)); + + challenger.observe(builder, num_variables_felt); let ood_points: Vec>> = - (0..self.config.starting_ood_samples) + (0..self.config.starting_ood_samples()) .map(|_| { (0..num_variables) .map(|_| challenger.sample_ext(builder)) @@ -268,8 +250,8 @@ impl> RecursiveWhirVerifier> RecursiveWhirVerifier = builder.constant(self.config.domain_generator); + let mut domain_size = self.config.starting_domain_log_size(); + let mut generator: Felt = builder.constant(self.config.domain_generator()); let mut prev_commitment = commitment; - let mut prev_folding_factor = num_variables - self.config.starting_interleaved_log_height; - let mut num_variables = self.config.starting_interleaved_log_height; + let mut prev_folding_factor = num_variables - self.config.starting_interleaved_log_height(); + let mut num_variables = self.config.starting_interleaved_log_height(); for round_index in 0..n_rounds { - let round_params = &self.config.round_parameters[round_index]; + let round_params = &self.config.round_parameters()[round_index]; let new_commitment = &proof.commitments[round_index + 1]; // Observe the round commitments @@ -462,7 +443,7 @@ impl> RecursiveWhirVerifier::as_symbolic(&generator).square()); @@ -475,9 +456,9 @@ impl> RecursiveWhirVerifier::as_symbolic(&final_poly)); - challenger.check_witness(builder, self.config.final_pow_bits, proof.final_pow); + challenger.check_witness(builder, self.config.final_pow_bits(), proof.final_pow); - let final_id_indices = (0..self.config.final_queries) + let final_id_indices = (0..self.config.final_queries()) .map(|_| challenger.sample_bits(builder, domain_size)) .collect::>(); let final_id_values: Vec> = final_id_indices @@ -524,8 +505,8 @@ impl> RecursiveWhirVerifier::new(Radix2DitParallel, merkle_prover, config.clone()); + challenger_prover.observe(::F::two()); config.write_to_challenger::<::Digest, _>(&mut challenger_prover); let merkle_verifier = MerkleTreeTcs::default(); let verifier = @@ -831,8 +815,8 @@ mod tests { let eval_claim: EF = polynomial_concat.eval_at(&eval_point)[0]; // Observe all commitments into both challengers. - verifier.observe_commitment(&commitments, &mut challenger_prover, 2).unwrap(); - verifier.observe_commitment(&commitments, &mut challenger_verifier, 2).unwrap(); + challenger_prover.observe_constant_length_digest_slice(&commitments); + challenger_verifier.observe_constant_length_digest_slice(&commitments); // Prove using prove_trusted_evaluation. let prover_datas = vec![prover_data_1, prover_data_2].into_iter().collect(); @@ -844,17 +828,10 @@ mod tests { let round_areas = proof .initial_merkle_proof .iter() - .map(|p| p.proof.width << config.starting_interleaved_log_height) + .map(|p| p.proof.width << config.starting_interleaved_log_height()) .collect::>(); let (point, value) = verifier - .verify( - &commitments, - &round_areas, - num_variables, - eval_claim, - &proof, - &mut challenger_verifier, - ) + .verify(&commitments, &round_areas, eval_claim, &proof, &mut challenger_verifier) .unwrap(); // Recursive circuit verification. @@ -872,6 +849,7 @@ mod tests { config.clone(), &mut builder, &mut challenger_variable, + 2, ); recursive_verifier.observe_commitment( diff --git a/crates/test-artifacts/programs/Cargo.lock b/crates/test-artifacts/programs/Cargo.lock index 7b81d1be6b..eb6dbff405 100644 --- a/crates/test-artifacts/programs/Cargo.lock +++ b/crates/test-artifacts/programs/Cargo.lock @@ -2064,7 +2064,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", - "thiserror", + "thiserror 2.0.16", "ucd-trie", ] @@ -2840,10 +2840,12 @@ name = "slop-challenger" version = "6.1.0" dependencies = [ "futures", + "num-bigint 0.4.6", "p3-challenger", "serde", "slop-algebra", "slop-symmetric", + "thiserror 1.0.69", ] [[package]] @@ -3191,13 +3193,33 @@ dependencies = [ "time", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.16", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", ] [[package]] diff --git a/crates/test-artifacts/programs/fibonacci-blake3/Cargo.lock b/crates/test-artifacts/programs/fibonacci-blake3/Cargo.lock index 957e3e506f..dca63156f0 100644 --- a/crates/test-artifacts/programs/fibonacci-blake3/Cargo.lock +++ b/crates/test-artifacts/programs/fibonacci-blake3/Cargo.lock @@ -1074,10 +1074,12 @@ name = "slop-challenger" version = "6.1.0" dependencies = [ "futures", + "num-bigint 0.4.6", "p3-challenger", "serde", "slop-algebra", "slop-symmetric", + "thiserror", ] [[package]] @@ -1222,6 +1224,26 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "tracing" version = "0.1.41" diff --git a/crates/verifier/guest-verify-programs/Cargo.lock b/crates/verifier/guest-verify-programs/Cargo.lock index 7e5dc91b65..6cf5839849 100644 --- a/crates/verifier/guest-verify-programs/Cargo.lock +++ b/crates/verifier/guest-verify-programs/Cargo.lock @@ -1731,10 +1731,12 @@ name = "slop-challenger" version = "6.1.0" dependencies = [ "futures", + "num-bigint 0.4.6", "p3-challenger", "serde", "slop-algebra", "slop-symmetric", + "thiserror 1.0.69", ] [[package]] @@ -1981,6 +1983,7 @@ dependencies = [ "derive-where", "futures", "itertools 0.14.0", + "num-bigint 0.4.6", "rand", "rayon", "serde", diff --git a/slop/crates/alloc/Cargo.toml b/slop/crates/alloc/Cargo.toml index 47db2b8c28..7455a2a4aa 100644 --- a/slop/crates/alloc/Cargo.toml +++ b/slop/crates/alloc/Cargo.toml @@ -15,5 +15,8 @@ slop-algebra = { workspace = true } thiserror = { workspace = true } serde = { workspace = true } +[dev-dependencies] +slop-koala-bear = { workspace = true } + [lints] workspace = true diff --git a/slop/crates/alloc/src/buffer.rs b/slop/crates/alloc/src/buffer.rs index 47956c9975..2c999b463d 100644 --- a/slop/crates/alloc/src/buffer.rs +++ b/slop/crates/alloc/src/buffer.rs @@ -327,7 +327,7 @@ where /// unsafe { /// // Initialize all 4 bytes /// buffer.as_mut_ptr().write_bytes(0, 4); - /// + /// /// // Now we can safely assume all memory is initialized /// buffer.assume_init(); /// } @@ -686,76 +686,6 @@ where Ok(()) } - - /// Reinterprets the buffer's elements as base field elements. - /// - /// This method consumes the buffer and returns a new buffer where each - /// extension field element is reinterpreted as `D` base field elements, - /// where `D` is the degree of the extension. - /// - /// # Type Parameters - /// - /// - `E`: The base field type - /// - `T`: Must implement `ExtensionField` - /// - /// # Examples - /// - /// ```rust,ignore - /// // If T is a degree-4 extension over E - /// let buffer: Buffer = buffer![ext1, ext2, ext3]; - /// let base_buffer: Buffer = buffer.flatten_to_base(); - /// assert_eq!(base_buffer.len(), 12); // 3 * 4 = 12 - /// ``` - pub fn flatten_to_base(self) -> Buffer - where - T: ExtensionField, - E: Field, - { - let mut buffer = ManuallyDrop::new(self); - let (original_ptr, original_len, original_cap, allocator) = - (buffer.as_mut_ptr(), buffer.len(), buffer.capacity(), buffer.allocator().clone()); - let ptr = original_ptr as *mut E; - let len = original_len * T::D; - let cap = original_cap * T::D; - unsafe { Buffer::from_raw_parts(ptr, len, cap, allocator) } - } - - /// Reinterprets the buffer's base field elements as extension field elements. - /// - /// This method consumes the buffer and returns a new buffer where every `D` - /// base field elements are reinterpreted as one extension field element, - /// where `D` is the degree of the extension. - /// - /// # Type Parameters - /// - /// - `T`: The base field type - /// - `E`: Must implement `ExtensionField` - /// - /// # Panics - /// - /// Panics if the buffer length is not divisible by the extension degree. - /// - /// # Examples - /// - /// ```rust,ignore - /// // If E is a degree-4 extension over T - /// let buffer: Buffer = buffer![b1, b2, b3, b4, b5, b6, b7, b8]; - /// let ext_buffer: Buffer = buffer.into_extension(); - /// assert_eq!(ext_buffer.len(), 2); // 8 / 4 = 2 - /// ``` - pub fn into_extension(self) -> Buffer - where - T: Field, - E: ExtensionField, - { - let mut buffer = ManuallyDrop::new(self); - let (original_ptr, original_len, original_cap, allocator) = - (buffer.as_mut_ptr(), buffer.len(), buffer.capacity(), buffer.allocator().clone()); - let ptr = original_ptr as *mut E; - let len = original_len.checked_div(E::D).unwrap(); - let cap = original_cap.checked_div(E::D).unwrap(); - unsafe { Buffer::from_raw_parts(ptr, len, cap, allocator) } - } } impl HasBackend for Buffer { @@ -1044,6 +974,67 @@ impl Buffer { vec.insert(index, value); *self = Self::from(vec); } + + /// Reinterprets the buffer's base field elements as extension field elements. + /// + /// This method consumes the buffer and returns a new buffer where every `D` + /// base field elements are reinterpreted as one extension field element, + /// where `D` is the degree of the extension. + /// + /// # Type Parameters + /// + /// - `T`: The base field type + /// - `E`: Must implement `ExtensionField` + /// + /// # Panics + /// + /// Panics if the buffer length is not divisible by the extension degree. + /// + /// # Examples + /// + /// ```rust,ignore + /// // If E is a degree-4 extension over T + /// let buffer: Buffer = buffer![b1, b2, b3, b4, b5, b6, b7, b8]; + /// let ext_buffer: Buffer = buffer.into_extension(); + /// assert_eq!(ext_buffer.len(), 2); // 8 / 4 = 2 + /// ``` + pub fn into_extension(self) -> Buffer + where + T: Field, + E: ExtensionField, + { + let self_vec = self.into_vec(); + let iter = self_vec.chunks_exact(E::D); + assert!(iter.clone().remainder().is_empty()); + iter.map(E::from_base_slice).collect() + } + + /// Reinterprets the buffer's elements as base field elements. + /// + /// This method consumes the buffer and returns a new buffer where each + /// extension field element is reinterpreted as `D` base field elements, + /// where `D` is the degree of the extension. + /// + /// # Type Parameters + /// + /// - `E`: The base field type + /// - `T`: Must implement `ExtensionField` + /// + /// # Examples + /// + /// ```rust,ignore + /// // If T is a degree-4 extension over E + /// let buffer: Buffer = buffer![ext1, ext2, ext3]; + /// let base_buffer: Buffer = buffer.flatten_to_base(); + /// assert_eq!(base_buffer.len(), 12); // 3 * 4 = 12 + /// ``` + pub fn flatten_to_base(self) -> Buffer + where + T: ExtensionField, + E: Field, + { + self.into_vec().iter().flat_map(T::as_base_slice).copied().collect() + } } impl From> for Buffer { @@ -1286,6 +1277,9 @@ impl<'de, T: Deserialize<'de>> Deserialize<'de> for Buffer { #[cfg(test)] mod tests { + use slop_algebra::{extension::BinomialExtensionField, AbstractField}; + use slop_koala_bear::KoalaBear; + use super::*; #[test] @@ -1347,4 +1341,13 @@ mod tests { assert_eq!(*buffer[4], 4); assert_eq!(*buffer[5], 4); } + + #[test] + #[should_panic] + fn test_into_extension() { + let buffer = buffer![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + let felt_buffer = + buffer.iter().copied().map(KoalaBear::from_canonical_usize).collect::>(); + let _ = felt_buffer.into_extension::>(); + } } diff --git a/slop/crates/challenger/Cargo.toml b/slop/crates/challenger/Cargo.toml index ab1928eacf..ee3daf347c 100644 --- a/slop/crates/challenger/Cargo.toml +++ b/slop/crates/challenger/Cargo.toml @@ -11,7 +11,9 @@ categories.workspace = true [dependencies] p3-challenger = { workspace = true } +thiserror = { workspace = true } slop-symmetric = { workspace = true } slop-algebra = { workspace = true } futures = { workspace = true } serde = { workspace = true } +num-bigint = { workspace = true } diff --git a/slop/crates/challenger/src/lib.rs b/slop/crates/challenger/src/lib.rs index 7355ef2bc5..c47ed3c8af 100644 --- a/slop/crates/challenger/src/lib.rs +++ b/slop/crates/challenger/src/lib.rs @@ -3,11 +3,13 @@ mod synchronize; use std::fmt::Debug; +use num_bigint::BigUint; pub use p3_challenger::*; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use slop_algebra::{ExtensionField, Field, PrimeField32}; use slop_symmetric::{CryptographicHasher, PseudoCompressionFunction}; pub use synchronize::*; +use thiserror::Error; pub trait FromChallenger: Sized { fn from_challenger(challenger: &Challenger, backend: &A) -> Self; @@ -51,18 +53,37 @@ pub trait IopCtx: fn default_challenger() -> Self::Challenger; } +#[derive(Error, Debug, Clone, Copy, Eq, PartialEq)] +#[error("usize out of field bounds")] +pub struct USizeOutOfFieldBounds; + pub trait VariableLengthChallenger: FieldChallenger + CanObserve { - fn observe_variable_length_slice(&mut self, data: &[F]) { - self.observe(F::from_canonical_u32(data.len() as u32)); + fn observe_variable_length_slice(&mut self, data: &[F]) -> Result<(), USizeOutOfFieldBounds> { + let data_len_big_uint = BigUint::from(data.len()); + + if data_len_big_uint >= F::order() { + return Err(USizeOutOfFieldBounds); + } + self.observe(F::from_canonical_usize(data.len())); self.observe_slice(data); + Ok(()) } - fn observe_variable_length_extension_slice>(&mut self, data: &[EF]) { - self.observe(F::from_canonical_u32(data.len() as u32)); + fn observe_variable_length_extension_slice>( + &mut self, + data: &[EF], + ) -> Result<(), USizeOutOfFieldBounds> { + let data_len_big_uint = BigUint::from(data.len()); + + if data_len_big_uint >= F::order() { + return Err(USizeOutOfFieldBounds); + } + self.observe(F::from_canonical_usize(data.len())); for &item in data { self.observe_ext_element(item); } + Ok(()) } fn observe_constant_length_slice(&mut self, data: &[F]) { self.observe_slice(data); @@ -75,9 +96,18 @@ pub trait VariableLengthChallenger: fn observe_constant_length_digest_slice(&mut self, data: &[Digest]) { self.observe_slice(data); } - fn observe_variable_length_digest_slice(&mut self, data: &[Digest]) { - self.observe(F::from_canonical_u32(data.len() as u32)); + fn observe_variable_length_digest_slice( + &mut self, + data: &[Digest], + ) -> Result<(), USizeOutOfFieldBounds> { + let data_len_big_uint = BigUint::from(data.len()); + + if data_len_big_uint >= F::order() { + return Err(USizeOutOfFieldBounds); + } + self.observe(F::from_canonical_usize(data.len())); self.observe_slice(data); + Ok(()) } } diff --git a/slop/crates/tensor/src/inner.rs b/slop/crates/tensor/src/inner.rs index fb2f3eb8ad..810659888a 100644 --- a/slop/crates/tensor/src/inner.rs +++ b/slop/crates/tensor/src/inner.rs @@ -226,26 +226,6 @@ impl Tensor { pub unsafe fn assume_init(&mut self) { self.storage.set_len(self.storage.capacity()); } - - pub fn flatten_to_base(self) -> Tensor - where - T: ExtensionField, - { - let [height, width]: [usize; 2] = self.sizes().try_into().unwrap(); - let dimensions = Dimensions::try_from([height, T::D * width]).unwrap(); - let data_storage = self.into_buffer().flatten_to_base(); - Tensor { storage: data_storage, dimensions } - } - - pub fn into_extension>(self) -> Tensor - where - T: Field, - { - let [height, width]: [usize; 2] = self.sizes().try_into().unwrap(); - let dimensions = Dimensions::try_from([height, width / ET::D]).unwrap(); - let extension_storage = self.into_buffer().into_extension(); - Tensor { storage: extension_storage, dimensions } - } } impl> Index for Tensor { @@ -353,6 +333,26 @@ impl Tensor { pub fn as_mut_slice(&mut self) -> &mut [T] { &mut self.storage[..] } + + pub fn into_extension>(self) -> Tensor + where + T: Field, + { + let [height, width]: [usize; 2] = self.sizes().try_into().unwrap(); + let dimensions = Dimensions::try_from([height, width / ET::D]).unwrap(); + let extension_storage = self.into_buffer().into_extension(); + Tensor { storage: extension_storage, dimensions } + } + + pub fn flatten_to_base(self) -> Tensor + where + T: ExtensionField, + { + let [height, width]: [usize; 2] = self.sizes().try_into().unwrap(); + let dimensions = Dimensions::try_from([height, T::D * width]).unwrap(); + let data_storage = self.into_buffer().flatten_to_base(); + Tensor { storage: data_storage, dimensions } + } } #[derive(Debug)] diff --git a/slop/crates/whir/Cargo.toml b/slop/crates/whir/Cargo.toml index e14be97fa0..7a5f0e6760 100644 --- a/slop/crates/whir/Cargo.toml +++ b/slop/crates/whir/Cargo.toml @@ -33,6 +33,7 @@ derive-where = { workspace = true } rayon = { workspace = true } futures = { workspace = true } itertools = { workspace = true } +num-bigint = { workspace = true } [dev-dependencies] slop-baby-bear = { workspace = true } diff --git a/slop/crates/whir/src/config.rs b/slop/crates/whir/src/config.rs index 4e8fbcff90..fa86a87136 100644 --- a/slop/crates/whir/src/config.rs +++ b/slop/crates/whir/src/config.rs @@ -1,175 +1,303 @@ +use num_bigint::BigUint; use serde::{Deserialize, Serialize}; -use slop_algebra::{Field, TwoAdicField}; +use slop_algebra::{ExtensionField, Field, PrimeField64, TwoAdicField}; use slop_challenger::VariableLengthChallenger; -/// A fully expanded WHIR configuration. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct WhirProofShape { - pub domain_generator: F, - - /// The OOD samples used in the commitment. +pub struct UncheckedWhirProofShape { pub starting_ood_samples: usize, - - /// The rate of the initial RS code used during the protocol. pub starting_log_inv_rate: usize, - - /// The initial folding factor. pub starting_interleaved_log_height: usize, - - /// The initial domain size - pub starting_domain_log_size: usize, - - /// The initial pow bits used in the first fold. pub starting_folding_pow_bits: Vec, - - /// The round-specific parameters. - pub round_parameters: Vec, - - /// Logarithm of the number of coefficients in the final polynomial. (The final polynomial - /// technically has degree `2^final_poly_log_degree - 1`.) - pub final_poly_log_degree: usize, - - /// Number of queries in the last round + pub round_parameters: Vec>, pub final_queries: usize, - - /// Number of final bits of proof of work (for the queries). - pub final_pow_bits: usize, - - /// Number of final bits of proof of work (for the sumcheck). pub final_folding_pow_bits: Vec, + pub final_pow_bits: usize, } -impl WhirProofShape { +impl UncheckedWhirProofShape { pub fn default_whir_config() -> Self { let folding_factor = 4; - WhirProofShape:: { - domain_generator: F::two_adic_generator(13), + Self { starting_ood_samples: 1, starting_log_inv_rate: 1, starting_interleaved_log_height: 12, - starting_domain_log_size: 13, starting_folding_pow_bits: vec![10; folding_factor], round_parameters: vec![ - RoundConfig { - folding_factor, - evaluation_domain_log_size: 12, - queries_pow_bits: 10, - pow_bits: vec![10; folding_factor], - num_queries: 90, - ood_samples: 1, - log_inv_rate: 4, - }, - RoundConfig { - folding_factor, - evaluation_domain_log_size: 11, - queries_pow_bits: 10, - pow_bits: vec![10; folding_factor], - num_queries: 15, - ood_samples: 1, - log_inv_rate: 7, - }, + RoundConfig::new(folding_factor, 10, vec![10; folding_factor], 90, 1), + RoundConfig::new(folding_factor, 10, vec![10; folding_factor], 15, 1), ], - final_poly_log_degree: 4, final_queries: 10, final_pow_bits: 10, final_folding_pow_bits: vec![10; 8], } } + pub fn big_beautiful_whir_config() -> Self { let folding_factor = 4; - WhirProofShape:: { - domain_generator: F::two_adic_generator(21), + Self { starting_ood_samples: 2, starting_log_inv_rate: 1, starting_interleaved_log_height: 20, - starting_domain_log_size: 21, starting_folding_pow_bits: vec![0; 10], round_parameters: vec![ - RoundConfig { - folding_factor, - evaluation_domain_log_size: 20, - queries_pow_bits: 16, - pow_bits: vec![0; folding_factor], - num_queries: 84, - ood_samples: 2, - log_inv_rate: 4, - }, - RoundConfig { - folding_factor, - evaluation_domain_log_size: 19, - queries_pow_bits: 16, - pow_bits: vec![0; folding_factor], - num_queries: 21, - ood_samples: 2, - log_inv_rate: 7, - }, - RoundConfig { - folding_factor, - evaluation_domain_log_size: 18, - queries_pow_bits: 16, - pow_bits: vec![0; folding_factor], - num_queries: 12, - ood_samples: 2, - log_inv_rate: 10, - }, + RoundConfig::new(folding_factor, 16, vec![0; folding_factor], 84, 2), + RoundConfig::new(folding_factor, 16, vec![0; folding_factor], 21, 2), ], - final_poly_log_degree: 8, final_queries: 9, + final_folding_pow_bits: vec![0; 12], final_pow_bits: 16, - final_folding_pow_bits: vec![0; 8], } } } -impl WhirProofShape { + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WhirProofShape { + /// The OOD samples used in the commitment. + starting_ood_samples: usize, + + /// The rate of the initial RS code used during the protocol. + starting_log_inv_rate: usize, + + /// The initial folding factor. + starting_interleaved_log_height: usize, + + /// The initial pow bits used in the first fold. + starting_folding_pow_bits: Vec, + + /// The round-specific parameters. + round_parameters: Vec>, + + /// Number of queries in the last round + final_queries: usize, + + /// Number of final bits of proof of work (for the queries). + final_pow_bits: usize, + + /// Number of final bits of proof of work (for the sumcheck). + final_folding_pow_bits: Vec, + + _marker: std::marker::PhantomData<(F, EF)>, +} + +impl> WhirProofShape { + pub fn new(config: UncheckedWhirProofShape) -> Self { + let starting_domain_log_size = config + .starting_interleaved_log_height + .checked_add(config.starting_log_inv_rate) + .unwrap(); + + assert!(starting_domain_log_size >= config.round_parameters.len(), "each STIR round reduces the domain size by 1, so starting_domain_log_size must be at least round_params.len()"); + assert!( + config.starting_interleaved_log_height < (usize::BITS as usize), + "starting_interleaved_log_height must be less than usize::BITS" + ); + assert!( + starting_domain_log_size < (usize::BITS as usize), + "starting_domain_log_size must be less than usize::BITS" + ); + assert!(1 << starting_domain_log_size < F::ORDER_U64); + + assert!(config + .starting_folding_pow_bits + .iter() + .all(|&bits| bits < (usize::BITS as usize) && 1 << bits < F::ORDER_U64)); + assert!(config + .final_folding_pow_bits + .iter() + .all(|&bits| bits < (usize::BITS as usize) && 1 << bits < F::ORDER_U64)); + assert!( + config.final_pow_bits < (usize::BITS as usize) + && 1 << config.final_pow_bits < F::ORDER_U64 + ); + + for round_param in &config.round_parameters { + assert!(round_param.folding_factor < usize::BITS as usize); + assert!(round_param.queries_pow_bits < (usize::BITS as usize)); + + // Check that the folding factor does not overflow when multiplied by EF::D + assert!(!(1usize << round_param.folding_factor).overflowing_mul(EF::D).1); + + assert!(1 << round_param.queries_pow_bits < F::ORDER_U64); + + assert!(round_param + .pow_bits + .iter() + .all(|&bits| bits < (usize::BITS as usize) && 1 << bits < F::ORDER_U64)); + } + + let num_folded_variables = + config.round_parameters.iter().map(|p| p.folding_factor).sum::(); + + assert!(config.starting_interleaved_log_height.checked_sub(num_folded_variables).is_some()); + + let result = Self { + starting_ood_samples: config.starting_ood_samples, + starting_log_inv_rate: config.starting_log_inv_rate, + starting_interleaved_log_height: config.starting_interleaved_log_height, + starting_folding_pow_bits: config.starting_folding_pow_bits, + round_parameters: config.round_parameters, + final_queries: config.final_queries, + final_folding_pow_bits: config.final_folding_pow_bits, + final_pow_bits: config.final_pow_bits, + _marker: std::marker::PhantomData, + }; + + // The check before calling the constructor guarantees no underflow in the subtraction in the function call. + assert!(result.final_poly_log_degree() < usize::BITS as usize); + + assert!(result.check_usizes_bound_by_field_order()); + + result + } +} +impl> WhirProofShape { + fn check_usizes_bound_by_field_order(&self) -> bool { + let &WhirProofShape { + starting_ood_samples, + starting_log_inv_rate, + starting_interleaved_log_height, + ref starting_folding_pow_bits, + ref round_parameters, + final_queries, + final_pow_bits, + ref final_folding_pow_bits, + .. + } = self; + let mut result = true; + let order = F::order(); + result &= BigUint::from(starting_ood_samples) < order; + result &= BigUint::from(starting_log_inv_rate) < order; + result &= BigUint::from(starting_interleaved_log_height) < order; + result &= BigUint::from(self.starting_domain_log_size()) < order; + result &= starting_folding_pow_bits.iter().all(|&b| BigUint::from(b) < order); + round_parameters.iter().for_each(|rp| { + let &RoundConfig { + folding_factor, + queries_pow_bits, + ref pow_bits, + num_queries, + ood_samples, + _marker, + } = rp; + result &= BigUint::from(folding_factor) < order + && BigUint::from(queries_pow_bits) < order + && pow_bits.iter().all(|&b| BigUint::from(b) < order) + && BigUint::from(num_queries) < order + && BigUint::from(ood_samples) < order; + }); + result &= BigUint::from(self.final_poly_log_degree()) < order; + result &= BigUint::from(final_queries) < order; + result &= BigUint::from(final_pow_bits) < order; + result &= final_folding_pow_bits.iter().all(|&b| BigUint::from(b) < order); + // Checks on vector lengths not overflowing field order. + result &= BigUint::from(round_parameters.len()) < F::order(); + result &= BigUint::from(final_folding_pow_bits.len()) < F::order(); + result &= BigUint::from(starting_folding_pow_bits.len()) < F::order(); + result &= round_parameters.iter().all(|p| BigUint::from(p.pow_bits.len()) < F::order()); + result + } pub fn write_to_challenger>( &self, challenger: &mut C, ) { let &WhirProofShape { - domain_generator, starting_ood_samples, starting_log_inv_rate, starting_interleaved_log_height, - starting_domain_log_size, ref starting_folding_pow_bits, ref round_parameters, - final_poly_log_degree, final_queries, final_pow_bits, ref final_folding_pow_bits, + _marker, } = self; - challenger.observe(domain_generator); challenger.observe(F::from_canonical_usize(starting_ood_samples)); challenger.observe(F::from_canonical_usize(starting_log_inv_rate)); challenger.observe(F::from_canonical_usize(starting_interleaved_log_height)); - challenger.observe(F::from_canonical_usize(starting_domain_log_size)); - challenger.observe_variable_length_slice( - &starting_folding_pow_bits - .iter() - .copied() - .map(F::from_canonical_usize) - .collect::>(), - ); + // It is ok to unwrap the error here because the error path is excluded at `WhirProofShape` + // initialization. + challenger + .observe_variable_length_slice( + &starting_folding_pow_bits + .iter() + .copied() + .map(F::from_canonical_usize) + .collect::>(), + ) + .unwrap(); + + challenger.observe(F::from_canonical_usize(round_parameters.len())); round_parameters.iter().for_each(|f| f.write_to_challenger(challenger)); - challenger.observe(F::from_canonical_usize(final_poly_log_degree)); challenger.observe(F::from_canonical_usize(final_queries)); challenger.observe(F::from_canonical_usize(final_pow_bits)); - challenger.observe_variable_length_slice( - &final_folding_pow_bits - .iter() - .copied() - .map(F::from_canonical_usize) - .collect::>(), - ); + // It is ok to unwrap the error here because the error path is excluded at `WhirProofShape` + // initialization. + challenger + .observe_variable_length_slice( + &final_folding_pow_bits + .iter() + .copied() + .map(F::from_canonical_usize) + .collect::>(), + ) + .unwrap(); + } + + pub fn domain_generator(&self) -> F + where + F: TwoAdicField, + { + F::two_adic_generator(self.starting_domain_log_size()) + } + + pub fn starting_ood_samples(&self) -> usize { + self.starting_ood_samples + } + + pub fn starting_log_inv_rate(&self) -> usize { + self.starting_log_inv_rate + } + + pub fn starting_interleaved_log_height(&self) -> usize { + self.starting_interleaved_log_height + } + + pub fn starting_domain_log_size(&self) -> usize { + self.starting_interleaved_log_height + self.starting_log_inv_rate + } + + pub fn starting_folding_pow_bits(&self) -> &[usize] { + &self.starting_folding_pow_bits + } + + pub fn round_parameters(&self) -> &[RoundConfig] { + &self.round_parameters + } + + pub fn final_poly_log_degree(&self) -> usize { + let num_folded_variables = + self.round_parameters.iter().map(|p| p.folding_factor).sum::(); + + self.starting_interleaved_log_height - num_folded_variables + } + + pub fn final_queries(&self) -> usize { + self.final_queries + } + + pub fn final_pow_bits(&self) -> usize { + self.final_pow_bits + } + + pub fn final_folding_pow_bits(&self) -> &[usize] { + &self.final_folding_pow_bits } } /// Round specific configuration #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct RoundConfig { +pub struct RoundConfig { /// Folding factor for this round. pub folding_factor: usize, - /// Size of evaluation domain (of oracle sent in this round) - pub evaluation_domain_log_size: usize, /// Number of bits of proof of work (for the queries). pub queries_pow_bits: usize, /// Number of bits of proof of work (for the folding). @@ -178,23 +306,39 @@ pub struct RoundConfig { pub num_queries: usize, /// Number of OOD samples in this round pub ood_samples: usize, - /// Rate of current RS codeword - pub log_inv_rate: usize, + // A marker to prevent accidental construction of `RoundConfig` without `new`. + _marker: std::marker::PhantomData, } -impl RoundConfig { - pub fn write_to_challenger>( - &self, - challenger: &mut C, - ) { +impl RoundConfig { + pub fn new( + folding_factor: usize, + queries_pow_bits: usize, + pow_bits: Vec, + num_queries: usize, + ood_samples: usize, + ) -> Self { + assert!(BigUint::from(pow_bits.len()) < F::order()); + Self { + folding_factor, + queries_pow_bits, + pow_bits, + num_queries, + ood_samples, + _marker: std::marker::PhantomData, + } + } + fn write_to_challenger>(&self, challenger: &mut C) { challenger.observe(F::from_canonical_usize(self.folding_factor)); - challenger.observe(F::from_canonical_usize(self.evaluation_domain_log_size)); challenger.observe(F::from_canonical_usize(self.queries_pow_bits)); - challenger.observe_variable_length_slice( - &self.pow_bits.iter().copied().map(F::from_canonical_usize).collect::>(), - ); + // It is ok to unwrap the result here because the error path is excluded when constructing + // the `RoundConfig` via the `new` function. + challenger + .observe_variable_length_slice( + &self.pow_bits.iter().copied().map(F::from_canonical_usize).collect::>(), + ) + .unwrap(); challenger.observe(F::from_canonical_usize(self.num_queries)); challenger.observe(F::from_canonical_usize(self.ood_samples)); - challenger.observe(F::from_canonical_usize(self.log_inv_rate)); } } diff --git a/slop/crates/whir/src/prover.rs b/slop/crates/whir/src/prover.rs index 8b6954446f..7bf45645f3 100644 --- a/slop/crates/whir/src/prover.rs +++ b/slop/crates/whir/src/prover.rs @@ -11,7 +11,7 @@ use crate::{ }; use derive_where::derive_where; use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator}; -use slop_algebra::{AbstractField, ExtensionField, Field}; +use slop_algebra::{AbstractField, ExtensionField, Field, TwoAdicField}; use slop_alloc::CpuBackend; use slop_challenger::{ CanObserve, CanSampleBits, FieldChallenger, GrindingChallenger, IopCtx, @@ -89,7 +89,7 @@ where { dft: D, merkle_prover: MerkleProver, - config: WhirProofShape, + config: WhirProofShape, _marker: std::marker::PhantomData, } @@ -148,17 +148,18 @@ impl Prover where GC: IopCtx, D: Dft, + GC::F: TwoAdicField, GC::Challenger: VariableLengthChallenger, MerkleProver: TensorCsProver + ComputeTcsOpenings, { - pub fn new(dft: D, merkle_prover: MerkleProver, config: WhirProofShape) -> Self { + pub fn new(dft: D, merkle_prover: MerkleProver, config: WhirProofShape) -> Self { Self { dft, merkle_prover, config, _marker: std::marker::PhantomData } } pub fn parse_commitment_data( &self, challenger: &mut GC::Challenger, - config: &WhirProofShape, + config: &WhirProofShape, rounds: Rounds>, ) -> WitnessData { let num_variables = rounds @@ -170,7 +171,7 @@ where let num_non_zero_entries: usize = rounds.iter().map(|r| r.polynomial.guts().as_slice().len()).sum(); - let ood_points: Vec> = (0..config.starting_ood_samples) + let ood_points: Vec> = (0..config.starting_ood_samples()) .map(|_| { (0..num_variables) .map(|_| challenger.sample_ext_element()) @@ -187,30 +188,29 @@ where .iter() .map(|r| { let num_entries = r.polynomial.guts().total_len(); - assert!( - num_entries.is_multiple_of(1 << config.starting_interleaved_log_height) - ); + assert!(num_entries + .is_multiple_of(1 << config.starting_interleaved_log_height())); r.polynomial.guts().clone().reshape([ - 1 << config.starting_interleaved_log_height, - num_entries / (1 << config.starting_interleaved_log_height), + 1 << config.starting_interleaved_log_height(), + num_entries / (1 << config.starting_interleaved_log_height()), ]) }) .chain(once(Tensor::from(vec![GC::F::zero(); num_to_add]).reshape([ - 1 << config.starting_interleaved_log_height, - num_to_add / (1 << config.starting_interleaved_log_height), + 1 << config.starting_interleaved_log_height(), + num_to_add / (1 << config.starting_interleaved_log_height()), ]))), ) .into_buffer() .to_vec(); assert_eq!(result.len(), 1 << num_variables); - for i in 0..(num_to_add >> config.starting_interleaved_log_height) { - for j in 0..(1 << config.starting_interleaved_log_height) { + for i in 0..(num_to_add >> config.starting_interleaved_log_height()) { + for j in 0..(1 << config.starting_interleaved_log_height()) { assert_eq!( - result[(num_non_zero_entries >> config.starting_interleaved_log_height) + result[(num_non_zero_entries >> config.starting_interleaved_log_height()) + i + (j << (num_variables - - config.starting_interleaved_log_height as u32))], + - config.starting_interleaved_log_height() as u32))], GC::F::zero() ); } @@ -221,8 +221,8 @@ where interleave_chain(rounds.iter().map(|r| { let num_entries = r.polynomial.guts().total_len(); r.polynomial.guts().clone().reshape([ - 1 << config.starting_interleaved_log_height, - num_entries / (1 << config.starting_interleaved_log_height), + 1 << config.starting_interleaved_log_height(), + num_entries / (1 << config.starting_interleaved_log_height()), ]) })) .into_buffer() @@ -266,9 +266,12 @@ where witness_data: Rounds>, claim: GC::EF, challenger: &mut GC::Challenger, - config: &WhirProofShape, + config: &WhirProofShape, ) -> WhirProof { - let n_rounds = config.round_parameters.len(); + let n_rounds = config.round_parameters().len(); + let num_variables = query_vector.num_variables() as usize; + + challenger.observe(GC::F::from_canonical_usize(num_variables)); let witness_data = self.parse_commitment_data(challenger, config, witness_data); @@ -282,8 +285,6 @@ where parsed_commitments.push(witness_data.parsed_commitment.clone()); - let num_variables = query_vector.num_variables() as usize; - let mut sumcheck_prover = SumcheckProver::::new( witness_data.polynomial.clone(), query_vector, @@ -294,25 +295,27 @@ where let (initial_sumcheck_polynomials, mut folding_randomness, mut claimed_sum) = sumcheck_prover.compute_sumcheck_polynomials( claimed_sum, - num_variables - config.starting_interleaved_log_height, - &config.starting_folding_pow_bits, + num_variables - config.starting_interleaved_log_height(), + config.starting_folding_pow_bits(), challenger, ); - let mut generator = config.domain_generator; + let mut generator = config.domain_generator(); let mut merkle_proofs = Vec::with_capacity(n_rounds); let mut query_proof_of_works = Vec::with_capacity(n_rounds); let mut sumcheck_polynomials = Vec::with_capacity(n_rounds); - let mut prev_domain_log_size = config.starting_domain_log_size; - let mut prev_folding_factor = num_variables - config.starting_interleaved_log_height; + let mut prev_domain_log_size = config.starting_domain_log_size(); + let mut prev_folding_factor = num_variables - config.starting_interleaved_log_height(); let (mut prev_prover_data, mut prev_committed_data) = ( witness_data.commitment_data, witness_data.committed_data.into_iter().map(Arc::new).collect::>(), ); + let mut num_remaining_variables = config.starting_interleaved_log_height(); + for round_index in 0..n_rounds { - let round_params = &config.round_parameters[round_index]; + let round_params = &config.round_parameters()[round_index]; let num_nonzero_entries = match &sumcheck_prover.f_vec { KOrEfMle::K(mle) => mle.inner().as_ref().unwrap().num_non_zero_entries(), @@ -326,8 +329,16 @@ where ]), }; - let encoding = - batch_dft::<_, GC::F, GC::EF>(&self.dft, inner_evals, round_params.log_inv_rate); + // We encode via a log inverse rate such that there are `1 << round_params.folding_factor` + // messages each of height `1<<(num_remaining_variables - round_params.folding_factor)` + // and the codewords have height `prev_domain_log_size -1`. + // Hence the log inverse rate is `prev_domain_log_size - 1 - (num_remaining_variables - round_params.folding_factor)`. + let log_inv_rate = + prev_domain_log_size - 1 - (num_remaining_variables - round_params.folding_factor); + // Update the number of remaining variables. + num_remaining_variables -= round_params.folding_factor; + + let encoding = batch_dft::<_, GC::F, GC::EF>(&self.dft, inner_evals, log_inv_rate); let encoding_base = encoding.flatten_to_base(); @@ -394,8 +405,6 @@ where let num_openings: usize = merkle_openings.iter().map(|o| o.sizes()[1]).sum(); - // assert!(num_openings <= 1 << prev_folding_factor); - let merkle_proof: Vec<_> = prev_prover_data .into_iter() .map(|data| { @@ -404,7 +413,7 @@ where .collect(); let merkle_proof = merkle_proof .into_iter() - .zip(merkle_openings.into_iter()) + .zip(merkle_openings) .map(|(proof, opening)| MerkleTreeOpeningAndProof { values: opening, proof }) .collect::>(); let merkle_read_values: Vec> = if round_index != 0 { @@ -469,7 +478,7 @@ where // Update generator = generator.square(); prev_folding_factor = round_params.folding_factor; - prev_domain_log_size = round_params.evaluation_domain_log_size; + prev_domain_log_size -= 1; (prev_prover_data, prev_committed_data) = ( vec![prover_data].into_iter().collect(), vec![Arc::new(encoding_base)].into_iter().collect(), @@ -484,13 +493,13 @@ where let mut final_polynomial = f_vec.inner().as_ref().unwrap().guts().clone().into_buffer().to_vec(); - final_polynomial.resize(1 << config.final_poly_log_degree, GC::EF::zero()); + final_polynomial.resize(1 << config.final_poly_log_degree(), GC::EF::zero()); challenger.observe_constant_length_extension_slice(&final_polynomial); - let final_pow = challenger.grind(config.final_pow_bits); + let final_pow = challenger.grind(config.final_pow_bits()); - let final_id_indices = (0..config.final_queries) + let final_id_indices = (0..config.final_queries()) .map(|_| challenger.sample_bits(prev_domain_log_size)) .collect::>(); @@ -509,8 +518,8 @@ where let (final_sumcheck_polynomials, _, _) = sumcheck_prover.compute_sumcheck_polynomials( claimed_sum, - config.final_poly_log_degree, - &config.final_folding_pow_bits, + config.final_poly_log_degree(), + config.final_folding_pow_bits(), challenger, ); @@ -538,6 +547,7 @@ impl MultilinearPcsProver> for Prover, + GC::F: TwoAdicField, MerkleProver: TensorCsProver + ComputeTcsOpenings, { type ProverData = WhirProverData; @@ -550,7 +560,7 @@ where ) -> Result<(::Digest, Self::ProverData, usize), Self::ProverError> { let len: usize = mles.iter().map(|mle| mle.guts().as_slice().len()).sum(); let added_zeroes = - len.next_multiple_of(1 << self.config.starting_interleaved_log_height) - len; + len.next_multiple_of(1 << self.config.starting_interleaved_log_height()) - len; let tensor: Tensor = mles .iter() .flat_map(|mle| mle.guts().transpose().as_slice().to_vec()) @@ -559,7 +569,7 @@ where .reshape([len + added_zeroes, 1]); let concatenated_mles = Mle::new(tensor); - let starting_interleaved_height = self.config.starting_interleaved_log_height; + let starting_interleaved_height = self.config.starting_interleaved_log_height(); let num_non_zero_entries = concatenated_mles.num_non_zero_entries(); let inner_evals = concatenated_mles @@ -571,7 +581,8 @@ where ]) .transpose(); - let encoding = batch_dft(&self.dft, inner_evals.clone(), self.config.starting_log_inv_rate); + let encoding = + batch_dft(&self.dft, inner_evals.clone(), self.config.starting_log_inv_rate()); let (commitment, prover_data) = self.merkle_prover.commit_tensors(encoding.clone().into()).unwrap(); @@ -594,7 +605,7 @@ where challenger: &mut ::Challenger, ) -> Result, Self::ProverError> { let (folding_point, stacked_point) = eval_point - .split_at(eval_point.dimension() - self.config.starting_interleaved_log_height); + .split_at(eval_point.dimension() - self.config.starting_interleaved_log_height()); let eval_point = stacked_point .iter() .copied() @@ -610,7 +621,7 @@ where } fn log_max_padding_amount(&self) -> u32 { - self.config.starting_interleaved_log_height as u32 + self.config.starting_interleaved_log_height() as u32 } } @@ -818,7 +829,7 @@ mod tests { use rand::{distributions::Standard, prelude::Distribution, thread_rng, Rng, SeedableRng}; use slop_algebra::{extension::BinomialExtensionField, TwoAdicField, UnivariatePolynomial}; - use slop_baby_bear::BabyBear; + use slop_baby_bear::{baby_bear_poseidon2::BabyBearDegree4Duplex, BabyBear}; use slop_commit::Rounds; use slop_dft::p3::Radix2DitParallel; use slop_jagged::{JaggedEvalSumcheckProver, JaggedPcsVerifier, JaggedProver}; @@ -831,7 +842,7 @@ mod tests { use slop_utils::setup_logger; use super::*; - use crate::{config::WhirProofShape, verifier::Verifier}; + use crate::{config::WhirProofShape, verifier::Verifier, UncheckedWhirProofShape}; type F = KoalaBear; type EF = BinomialExtensionField; @@ -1137,7 +1148,7 @@ mod tests { GC: IopCtx>, MerkleProver: TensorCsProver + ComputeTcsOpenings, >( - config: WhirProofShape, + config: WhirProofShape, merkle_prover: MerkleProver, rounds: Rounds>>, ) where @@ -1152,7 +1163,7 @@ mod tests { .iter() .map(|m| m.guts().as_slice().len()) .sum::() - .next_multiple_of(1 << config.starting_interleaved_log_height) + .next_multiple_of(1 << config.starting_interleaved_log_height()) }) .collect::>(); let num_variables = round_areas.iter().sum::().next_power_of_two().ilog2() as usize; @@ -1160,6 +1171,7 @@ mod tests { let mut rng = rand::rngs::StdRng::seed_from_u64(42); let mut challenger_prover = GC::default_challenger(); + challenger_prover.observe(GC::F::from_canonical_usize(rounds.len())); config.write_to_challenger(&mut challenger_prover); let mut challenger_verifier = GC::default_challenger(); @@ -1213,9 +1225,9 @@ mod tests { rounds.iter().count(), &mut challenger_verifier, ); - verifier - .observe_commitment(&commitments, &mut challenger_verifier, rounds.iter().count()) - .unwrap(); + + challenger_verifier.observe_constant_length_digest_slice(&commitments); + verifier .verify_trusted_evaluation( &commitments, @@ -1232,7 +1244,7 @@ mod tests { GC: IopCtx>, MerkleProver: TensorCsProver + ComputeTcsOpenings, >( - config: WhirProofShape, + config: WhirProofShape, num_variables: usize, merkle_prover: MerkleProver, ) where @@ -1258,7 +1270,7 @@ mod tests { GC: IopCtx>, MerkleProver: TensorCsProver + ComputeTcsOpenings, >( - config: WhirProofShape, + config: WhirProofShape, num_variables: usize, merkle_prover: MerkleProver, ) where @@ -1283,7 +1295,8 @@ mod tests { #[test] fn whir_test_multi_round_koala_bear() { - let config = WhirProofShape::default_whir_config(); + let config = UncheckedWhirProofShape::::default_whir_config(); + let config = WhirProofShape::>::new(config); let merkle_prover: Poseidon2KoalaBear16Prover = FieldMerkleTreeProver::default(); whir_test_multi_round::<_, _>(config, 16, merkle_prover); @@ -1291,7 +1304,8 @@ mod tests { #[test] fn whir_test_e2e_koala_bear() { - let config = WhirProofShape::default_whir_config(); + let config = UncheckedWhirProofShape::::default_whir_config(); + let config = WhirProofShape::>::new(config); let merkle_prover: Poseidon2KoalaBear16Prover = FieldMerkleTreeProver::default(); whir_test_single_round::<_, _>(config, 16, merkle_prover); } @@ -1299,29 +1313,33 @@ mod tests { #[test] #[ignore = "test used for benchmarking"] fn whir_test_realistic_koala_bear() { - let config = WhirProofShape::::big_beautiful_whir_config(); + let config = UncheckedWhirProofShape::::big_beautiful_whir_config(); + let config = WhirProofShape::>::new(config); let merkle_prover: Poseidon2KoalaBear16Prover = FieldMerkleTreeProver::default(); whir_test_single_round::<_, _>(config, 28, merkle_prover); } #[test] fn whir_test_e2e_baby_bear() { - let config = WhirProofShape::default_whir_config(); + let config = UncheckedWhirProofShape::::default_whir_config(); + let config = WhirProofShape::>::new(config); let merkle_prover: Poseidon2BabyBear16Prover = FieldMerkleTreeProver::default(); - whir_test_single_round::<_, _>(config, 16, merkle_prover); + whir_test_single_round::(config, 16, merkle_prover); } #[test] #[ignore = "test used for benchmarking"] fn whir_test_realistic_baby_bear() { - let config = WhirProofShape::big_beautiful_whir_config(); + let config = UncheckedWhirProofShape::::big_beautiful_whir_config(); + let config = WhirProofShape::>::new(config); let merkle_prover: Poseidon2BabyBear16Prover = FieldMerkleTreeProver::default(); whir_test_single_round::<_, _>(config, 28, merkle_prover); } #[test] fn jagged_whir_test_baby_bear() { - let config = WhirProofShape::default_whir_config(); + let config = UncheckedWhirProofShape::::default_whir_config(); + let config = WhirProofShape::>::new(config); let merkle_prover: Poseidon2BabyBear16Prover = FieldMerkleTreeProver::default(); test_jagged_whir_generic::<_, _>(config, merkle_prover); @@ -1331,7 +1349,7 @@ mod tests { GC: IopCtx>, MerkleProver: TensorCsProver + ComputeTcsOpenings, >( - config: WhirProofShape, + config: WhirProofShape, merkle_prover: MerkleProver, ) where rand::distributions::Standard: rand::distributions::Distribution, @@ -1385,6 +1403,7 @@ mod tests { let mut challenger_prover = jagged_verifier.challenger(); // Write the config to the challenger before proving. + challenger_prover.observe(GC::F::from_canonical_usize(num_rounds)); config.write_to_challenger(&mut challenger_prover); let jagged_prover = diff --git a/slop/crates/whir/src/verifier.rs b/slop/crates/whir/src/verifier.rs index 02ca8f9831..1323bcd078 100644 --- a/slop/crates/whir/src/verifier.rs +++ b/slop/crates/whir/src/verifier.rs @@ -1,8 +1,10 @@ -use itertools::Itertools; use std::iter::once; +use num_bigint::BigUint; use serde::{Deserialize, Serialize}; -use slop_algebra::{AbstractExtensionField, AbstractField, UnivariatePolynomial}; +use slop_algebra::{ + AbstractExtensionField, AbstractField, Field, TwoAdicField, UnivariatePolynomial, +}; use slop_challenger::{ CanObserve, CanSampleBits, FieldChallenger, GrindingChallenger, IopCtx, VariableLengthChallenger, @@ -10,7 +12,7 @@ use slop_challenger::{ use slop_commit::Rounds; use slop_merkle_tree::{MerkleTreeOpeningAndProof, MerkleTreeTcs}; use slop_multilinear::{Mle, MultilinearPcsVerifier, Point}; -use slop_utils::reverse_bits_len; +use slop_utils::{log2_ceil_usize, reverse_bits_len}; use thiserror::Error; use crate::{config::WhirProofShape, interleave_chain}; @@ -20,7 +22,7 @@ pub struct Verifier where GC: IopCtx, { - pub config: WhirProofShape, + pub config: WhirProofShape, merkle_verifier: MerkleTreeTcs, pub num_expected_commitments: usize, } @@ -103,6 +105,10 @@ pub enum WhirProofError { InvalidNumberOfCommitments(usize, usize), #[error("proof has incorrect shape")] IncorrectShape, + #[error("number of variables does not exceed starting_interleaved_log_height")] + StartingInterleavedLogHeightExceedsNumVariables, + #[error("the total area overflows usize")] + AreaOverflow, } impl From<(SumcheckError, usize)> for WhirProofError { @@ -137,46 +143,61 @@ pub fn map_to_pow(mut elem: F, len: usize) -> Point { impl Verifier where GC: IopCtx, + GC::F: TwoAdicField, GC::Challenger: VariableLengthChallenger, { + /// Construct a new WHIR verifier for a given proof shape and number of commitments. + /// + /// It is important to call this function *before* absorbing the WHIR commitments into the challenger. + /// This ensures that the challenger is correctly seeded with protocol shape before the main protocol starts. + /// The Fiat-Shamir challenger passed here by reference should then be used as the challenger for the main protocol. + /// + /// An example of unsound usage: observe a slice of `commitments` into a `challenger` -> pass that + /// challenger to the `Verifier::new` function -> call `Self::verify` with that challenger and + /// that slice of `commitments`. The first two steps must be swapped for correct Fiat-Shamir seeding. + /// + /// # Panics + /// + /// - Panics if `num_expected_commitments` is zero or larger than or equal to 4096 (this is a somewhat large bound that provides some DOS protection). + /// - Panics if the field order is smaller than 2^12 because `num_expected_commitments` should not exceed the field size for the challenger observation. pub fn new( merkle_verifier: MerkleTreeTcs, - config: WhirProofShape, + config: WhirProofShape, num_expected_commitments: usize, challenger: &mut GC::Challenger, ) -> Self { assert_ne!(num_expected_commitments, 0, "commitment must exist"); + assert!(GC::F::order() >= BigUint::from((1 << 12) as u64), "field order too small"); + assert!(num_expected_commitments < 1 << 12, "too many commitments"); + challenger.observe(GC::F::from_canonical_usize(num_expected_commitments)); config.write_to_challenger::(challenger); Self { merkle_verifier, config, num_expected_commitments } } - pub fn observe_commitment( - &self, - commitment: &[GC::Digest], - challenger: &mut GC::Challenger, - expected_length: usize, - ) -> Result<(), WhirProofError> { - if commitment.len() != expected_length { - Err(WhirProofError::InvalidNumberOfCommitments(expected_length, commitment.len())) - } else { - challenger.observe_constant_length_digest_slice(commitment); - Ok(()) - } - } - /// The claim is that < f, v > = claim. /// WHIR reduces it to a claim that v(point) = claim' pub fn verify( &self, commitments: &[GC::Digest], round_areas: &[usize], - num_variables: usize, claim: GC::EF, proof: &WhirProof, challenger: &mut GC::Challenger, ) -> Result<(Point, GC::EF), WhirProofError> { let config = &self.config; - let n_rounds = config.round_parameters.len(); + let n_rounds = config.round_parameters().len(); + + let mut total_area: usize = 0; + for area in round_areas { + total_area = total_area.checked_add(*area).ok_or(WhirProofError::AreaOverflow)?; + } + + let num_variables = log2_ceil_usize(total_area); + challenger.observe(GC::F::from_canonical_usize(num_variables)); + + if num_variables < config.starting_interleaved_log_height() { + return Err(WhirProofError::StartingInterleavedLogHeightExceedsNumVariables); + } if n_rounds == 0 || proof.merkle_proofs.len() != n_rounds - 1 @@ -196,25 +217,19 @@ where )); } + if !round_areas + .iter() + .all(|area| area.is_multiple_of(1 << self.config.starting_interleaved_log_height())) + { + return Err(WhirProofError::IncorrectShape); + } + let expected_widths = round_areas .iter() - .map(|area| (*area) >> self.config.starting_interleaved_log_height) + .map(|area| (*area) >> self.config.starting_interleaved_log_height()) .collect::>(); - for (merkle_proof, area) in proof.initial_merkle_proof.iter().zip_eq(round_areas.iter()) { - if merkle_proof.proof.width << self.config.starting_interleaved_log_height != *area { - println!( - "proof width: {}, proof log height: {}, expected area {}, area: {}", - merkle_proof.proof.width, - merkle_proof.proof.log_tensor_height, - area, - merkle_proof.proof.width << merkle_proof.proof.log_tensor_height - ); - return Err(WhirProofError::IncorrectShape); - } - } - - let ood_points: Vec> = (0..config.starting_ood_samples) + let ood_points: Vec> = (0..config.starting_ood_samples()) .map(|_| { (0..num_variables) .map(|_| challenger.sample_ext_element()) @@ -236,9 +251,9 @@ where } // Check that the number of OOD answers in the proof matches the expected value. - if commitment.ood_answers.len() != config.starting_ood_samples { + if commitment.ood_answers.len() != config.starting_ood_samples() { return Err(WhirProofError::InvalidNumberOfOODSamples( - config.starting_ood_samples, + config.starting_ood_samples(), commitment.ood_answers.len(), )); } @@ -262,8 +277,8 @@ where .verify_sumcheck( &proof.initial_sumcheck_polynomials, claimed_sum, - num_variables - config.starting_interleaved_log_height, - &config.starting_folding_pow_bits, + num_variables - config.starting_interleaved_log_height(), + config.starting_folding_pow_bits(), challenger, ) .map_err(|err| (err, 0))?; @@ -277,15 +292,16 @@ where // This is relative to the previous commitment (i.e. prev_commitment has a domain size of // this size) - let mut domain_size = config.starting_interleaved_log_height + config.starting_log_inv_rate; - let mut generator = config.domain_generator; + let mut domain_size = config.starting_domain_log_size(); + + let mut generator = config.domain_generator(); let mut prev_commitment = commitment; - let mut prev_folding_factor = num_variables - config.starting_interleaved_log_height; - let mut num_variables = config.starting_interleaved_log_height; + let mut prev_folding_factor = num_variables - config.starting_interleaved_log_height(); + let mut num_variables = config.starting_interleaved_log_height(); for round_index in 0..n_rounds { - let round_params = &config.round_parameters[round_index]; + let round_params = &config.round_parameters()[round_index]; // Because of the length checks at the start of the verification, the checked access isn't // expected to produce an error. let new_commitment = @@ -297,6 +313,10 @@ where )); } + if new_commitment.commitment.len() != 1 { + return Err(WhirProofError::IncorrectShape); + } + // Observe the commitments new_commitment.commitment.iter().for_each(|c| { challenger.observe(*c); @@ -352,14 +372,10 @@ where let expected_width = if round_index == 0 { expected_widths[i] } else { - (1 << config.round_parameters[round_index - 1].folding_factor) * GC::EF::D + (1 << config.round_parameters()[round_index - 1].folding_factor) * GC::EF::D }; - let expected_log_height = if round_index == 0 { - config.starting_interleaved_log_height + config.starting_log_inv_rate - } else { - config.round_parameters[round_index - 1].evaluation_domain_log_size - }; + let expected_log_height = domain_size; self.merkle_verifier .verify_tensor_openings( merkle_commitment, @@ -384,9 +400,8 @@ where proof .values .clone() - .into_buffer() .into_extension::() - .to_vec() + .into_buffer() .chunks_exact(1 << prev_folding_factor) .map(|v| Mle::new(v.to_vec().into())) .collect::>() @@ -448,20 +463,17 @@ where .concat(), ); - domain_size = round_params.evaluation_domain_log_size; prev_commitment = new_commitment; prev_folding_factor = round_params.folding_factor; generator = generator.square(); num_variables -= round_params.folding_factor; - if prev_commitment.commitment.len() != 1 { - return Err(WhirProofError::IncorrectShape); - } + domain_size -= 1; } // Now, we want to verify the final evaluations - if proof.final_polynomial.len() != 1 << config.final_poly_log_degree { + if proof.final_polynomial.len() != 1 << config.final_poly_log_degree() { return Err(WhirProofError::InvalidDegreeFinalPolynomial( - 1 << config.final_poly_log_degree, + 1 << config.final_poly_log_degree(), proof.final_polynomial.len(), )); } @@ -471,11 +483,11 @@ where let final_poly = proof.final_polynomial.clone(); let final_poly_uv = UnivariatePolynomial::new(final_poly.clone()); - if !challenger.check_witness(config.final_pow_bits, proof.final_pow) { + if !challenger.check_witness(config.final_pow_bits(), proof.final_pow) { return Err(WhirProofError::PowError); } - let final_id_indices = (0..config.final_queries) + let final_id_indices = (0..config.final_queries()) .map(|_| challenger.sample_bits(domain_size)) .collect::>(); let final_id_values: Vec = final_id_indices @@ -489,8 +501,8 @@ where &prev_commitment.commitment[0], &final_id_indices, &proof.final_merkle_opening_and_proof.values, - (1 << config.round_parameters[n_rounds - 1].folding_factor) * GC::EF::D, - config.round_parameters[n_rounds - 1].evaluation_domain_log_size, + (1 << config.round_parameters()[n_rounds - 1].folding_factor) * GC::EF::D, + domain_size, &proof.final_merkle_opening_and_proof.proof, ) .map_err(|_| WhirProofError::InvalidMerkleAuthentication)?; @@ -501,7 +513,6 @@ where .clone() .into_buffer() .into_extension::() - .to_vec() .chunks_exact(1 << prev_folding_factor) .map(|v| Mle::new(v.to_vec().into())) .collect(); @@ -525,8 +536,8 @@ where .verify_sumcheck( &proof.final_sumcheck_polynomials, claimed_sum, - config.final_poly_log_degree, - &config.final_folding_pow_bits, + config.final_poly_log_degree(), + config.final_folding_pow_bits(), challenger, ) .map_err(|err| (err, n_rounds + 1))?; @@ -607,6 +618,7 @@ where impl MultilinearPcsVerifier for Verifier where GC: IopCtx, + GC::F: TwoAdicField, { type Proof = WhirProof; @@ -625,16 +637,10 @@ where proof: &Self::Proof, challenger: &mut ::Challenger, ) -> Result<(), Self::VerifierError> { - let (randomness, claimed_value) = self.verify( - commitments, - round_polynomial_sizes, - point.dimension(), - evaluation_claims, - proof, - challenger, - )?; + let (randomness, claimed_value) = + self.verify(commitments, round_polynomial_sizes, evaluation_claims, proof, challenger)?; let (folding_point, stacking_point) = - point.split_at(point.dimension() - self.config.starting_interleaved_log_height); + point.split_at(point.dimension() - self.config.starting_interleaved_log_height()); let point = stacking_point .iter() .copied() @@ -649,7 +655,7 @@ where /// The jagged verifier will assume that the underlying PCS will pad commitments to a multiple /// of `1< u32 { - verifier.config.starting_interleaved_log_height as u32 + verifier.config.starting_interleaved_log_height() as u32 } } diff --git a/sp1-gpu/crates/logup_gkr/src/lib.rs b/sp1-gpu/crates/logup_gkr/src/lib.rs index 5ae71b9d64..6a2586b12a 100644 --- a/sp1-gpu/crates/logup_gkr/src/lib.rs +++ b/sp1-gpu/crates/logup_gkr/src/lib.rs @@ -225,9 +225,11 @@ where let host_numerator = numerator.to_host().unwrap(); let host_denominator = denominator.to_host().unwrap(); challenger - .observe_variable_length_extension_slice(host_numerator.guts().as_buffer().as_slice()); + .observe_variable_length_extension_slice(host_numerator.guts().as_buffer().as_slice()) + .unwrap(); challenger - .observe_variable_length_extension_slice(host_denominator.guts().as_buffer().as_slice()); + .observe_variable_length_extension_slice(host_denominator.guts().as_buffer().as_slice()) + .unwrap(); let output_host = LogUpGkrOutput { numerator: host_numerator, denominator: host_denominator }; @@ -277,9 +279,11 @@ where // Observe the openings. if let Some(prep_eval) = openings.preprocessed_trace_evaluations.as_ref() { - challenger.observe_variable_length_extension_slice(prep_eval); + challenger.observe_variable_length_extension_slice(prep_eval).unwrap(); } - challenger.observe_variable_length_extension_slice(&openings.main_trace_evaluations); + challenger + .observe_variable_length_extension_slice(&openings.main_trace_evaluations) + .unwrap(); chip_evaluations.insert(chip.name().to_string(), openings); } diff --git a/sp1-gpu/crates/zerocheck/src/lib.rs b/sp1-gpu/crates/zerocheck/src/lib.rs index 8aea9ece74..7aa2bfc4a8 100644 --- a/sp1-gpu/crates/zerocheck/src/lib.rs +++ b/sp1-gpu/crates/zerocheck/src/lib.rs @@ -675,14 +675,14 @@ where local: individual_column_evals[preprocessed_ptr..preprocessed_ptr + preprocessed_width] .to_vec(), }; - challenger.observe_variable_length_extension_slice(&preprocessed.local); + challenger.observe_variable_length_extension_slice(&preprocessed.local).unwrap(); preprocessed_ptr += preprocessed_width; let width = chip.width(); let main = AirOpenedValues { local: individual_column_evals[main_ptr..main_ptr + width].to_vec() }; - challenger.observe_variable_length_extension_slice(&main.local); + challenger.observe_variable_length_extension_slice(&main.local).unwrap(); main_ptr += width; opened_values.insert( @@ -1451,8 +1451,10 @@ pub mod tests { // Observe the openings for (_, opening) in opened_values.chips.iter() { - challenger.observe_variable_length_extension_slice(&opening.preprocessed.local); - challenger.observe_variable_length_extension_slice(&opening.main.local); + challenger + .observe_variable_length_extension_slice(&opening.preprocessed.local) + .unwrap(); + challenger.observe_variable_length_extension_slice(&opening.main.local).unwrap(); } }