diff --git a/air/src/lib.rs b/air/src/lib.rs index 539a812d9..0a471a706 100644 --- a/air/src/lib.rs +++ b/air/src/lib.rs @@ -38,7 +38,7 @@ mod errors; pub use errors::AssertionError; mod options; -pub use options::{FieldExtension, ProofOptions}; +pub use options::{FieldExtension, PartitionOptions, ProofOptions}; mod air; pub use air::{ diff --git a/air/src/options.rs b/air/src/options.rs index 7e71450bd..a831bdad7 100644 --- a/air/src/options.rs +++ b/air/src/options.rs @@ -4,9 +4,10 @@ // LICENSE file in the root directory of this source tree. use alloc::vec::Vec; +use core::{cmp, ops::Div}; use fri::FriOptions; -use math::{StarkField, ToElements}; +use math::{FieldElement, StarkField, ToElements}; use utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; // CONSTANTS @@ -74,6 +75,17 @@ pub enum FieldExtension { /// is the hash function used in the protocol. The soundness of a STARK proof is limited by the /// collision resistance of the hash function used by the protocol. For example, if a hash function /// with 128-bit collision resistance is used, soundness of a STARK proof cannot exceed 128 bits. +/// +/// In addition to the above, the parameter `num_partitions` is used in order to specify the number +/// of partitions each of the traces committed to during proof generation is split into, and +/// the parameter `min_partition_size` gives a lower bound on the size of each such partition. +/// More precisely, and taking the main segment trace as an example, the prover will split the main +/// segment trace into `num_partitions` parts each of size at least `min_partition_size`. The prover +/// will then proceed to hash each part row-wise resulting in `num_partitions` digests per row of +/// the trace. The prover finally combines the `num_partitions` digest (per row) into one digest +/// (per row) and at this point the vector commitment scheme can be called. +/// In the case when `num_partitions` is equal to `1` the prover will just hash each row in one go +/// producing one digest per row of the trace. #[derive(Debug, Clone, Eq, PartialEq)] pub struct ProofOptions { num_queries: u8, @@ -82,6 +94,7 @@ pub struct ProofOptions { field_extension: FieldExtension, fri_folding_factor: u8, fri_remainder_max_degree: u8, + partition_options: PartitionOptions, } // PROOF OPTIONS IMPLEMENTATION @@ -108,7 +121,6 @@ impl ProofOptions { /// - `grinding_factor` is greater than 32. /// - `fri_folding_factor` is not 2, 4, 8, or 16. /// - `fri_remainder_max_degree` is greater than 255 or is not a power of two minus 1. - #[rustfmt::skip] pub const fn new( num_queries: usize, blowup_factor: usize, @@ -125,11 +137,20 @@ impl ProofOptions { assert!(blowup_factor >= MIN_BLOWUP_FACTOR, "blowup factor cannot be smaller than 2"); assert!(blowup_factor <= MAX_BLOWUP_FACTOR, "blowup factor cannot be greater than 128"); - assert!(grinding_factor <= MAX_GRINDING_FACTOR, "grinding factor cannot be greater than 32"); + assert!( + grinding_factor <= MAX_GRINDING_FACTOR, + "grinding factor cannot be greater than 32" + ); assert!(fri_folding_factor.is_power_of_two(), "FRI folding factor must be a power of 2"); - assert!(fri_folding_factor >= FRI_MIN_FOLDING_FACTOR, "FRI folding factor cannot be smaller than 2"); - assert!(fri_folding_factor <= FRI_MAX_FOLDING_FACTOR, "FRI folding factor cannot be greater than 16"); + assert!( + fri_folding_factor >= FRI_MIN_FOLDING_FACTOR, + "FRI folding factor cannot be smaller than 2" + ); + assert!( + fri_folding_factor <= FRI_MAX_FOLDING_FACTOR, + "FRI folding factor cannot be greater than 16" + ); assert!( (fri_remainder_max_degree + 1).is_power_of_two(), @@ -140,16 +161,33 @@ impl ProofOptions { "FRI polynomial remainder degree cannot be greater than 255" ); - ProofOptions { + Self { num_queries: num_queries as u8, blowup_factor: blowup_factor as u8, grinding_factor: grinding_factor as u8, field_extension, fri_folding_factor: fri_folding_factor as u8, fri_remainder_max_degree: fri_remainder_max_degree as u8, + partition_options: PartitionOptions::new(1, 1), } } + /// Updates the provided [ProofOptions] instance with the specified partition parameters. + /// + /// # Panics + /// Panics if: + /// - `num_partitions` is zero or greater than 16. + /// - `min_partition_size` is zero or greater than 256. + pub const fn with_partitions( + mut self, + num_partitions: usize, + min_partition_size: usize, + ) -> ProofOptions { + self.partition_options = PartitionOptions::new(num_partitions, min_partition_size); + + self + } + // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- @@ -206,6 +244,11 @@ impl ProofOptions { let remainder_max_degree = self.fri_remainder_max_degree as usize; FriOptions::new(self.blowup_factor(), folding_factor, remainder_max_degree) } + + /// Returns the `[PartitionOptions]` used in this instance of proof options. + pub fn partition_options(&self) -> PartitionOptions { + self.partition_options + } } impl ToElements for ProofOptions { @@ -233,6 +276,8 @@ impl Serializable for ProofOptions { target.write(self.field_extension); target.write_u8(self.fri_folding_factor); target.write_u8(self.fri_remainder_max_degree); + target.write_u8(self.partition_options.num_partitions); + target.write_u8(self.partition_options.min_partition_size); } } @@ -242,14 +287,15 @@ impl Deserializable for ProofOptions { /// # Errors /// Returns an error of a valid proof options could not be read from the specified `source`. fn read_from(source: &mut R) -> Result { - Ok(ProofOptions::new( + let result = ProofOptions::new( source.read_u8()? as usize, source.read_u8()? as usize, source.read_u8()? as u32, FieldExtension::read_from(source)?, source.read_u8()? as usize, source.read_u8()? as usize, - )) + ); + Ok(result.with_partitions(source.read_u8()? as usize, source.read_u8()? as usize)) } } @@ -272,9 +318,6 @@ impl FieldExtension { } } -// SERIALIZATION -// ================================================================================================ - impl Serializable for FieldExtension { /// Serializes `self` and writes the resulting bytes into the `target`. fn write_into(&self, target: &mut W) { @@ -301,6 +344,55 @@ impl Deserializable for FieldExtension { } } +// PARTITION OPTION IMPLEMENTATION +// ================================================================================================ + +/// Defines the parameters used when committing to the traces generated during the protocol. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub struct PartitionOptions { + num_partitions: u8, + min_partition_size: u8, +} + +impl PartitionOptions { + /// Returns a new instance of `[PartitionOptions]`. + pub const fn new(num_partitions: usize, min_partition_size: usize) -> Self { + assert!(num_partitions >= 1, "number of partitions must be greater than or eqaul to 1"); + assert!(num_partitions <= 16, "number of partitions must be smaller than or equal to 16"); + + assert!( + min_partition_size >= 1, + "smallest partition size must be greater than or equal to 1" + ); + assert!( + min_partition_size <= 256, + "smallest partition size must be smaller than or equal to 256" + ); + + Self { + num_partitions: num_partitions as u8, + min_partition_size: min_partition_size as u8, + } + } + + /// Returns the size of each partition used when committing to the main and auxiliary traces as + /// well as the constraint evaluation trace. + pub fn partition_size(&self, num_columns: usize) -> usize { + let base_elements_per_partition = cmp::max( + (num_columns * E::EXTENSION_DEGREE).div_ceil(self.num_partitions as usize), + self.min_partition_size as usize, + ); + + base_elements_per_partition.div(E::EXTENSION_DEGREE) + } +} + +impl Default for PartitionOptions { + fn default() -> Self { + Self { num_partitions: 1, min_partition_size: 1 } + } +} + // TESTS // ================================================================================================ diff --git a/crypto/src/hash/blake/mod.rs b/crypto/src/hash/blake/mod.rs index 9b52cefef..6f17d9fdd 100644 --- a/crypto/src/hash/blake/mod.rs +++ b/crypto/src/hash/blake/mod.rs @@ -34,6 +34,10 @@ impl Hasher for Blake3_256 { ByteDigest(blake3::hash(ByteDigest::digests_as_bytes(values)).into()) } + fn merge_many(values: &[Self::Digest]) -> Self::Digest { + ByteDigest(blake3::hash(ByteDigest::digests_as_bytes(values)).into()) + } + fn merge_with_int(seed: Self::Digest, value: u64) -> Self::Digest { let mut data = [0; 40]; data[..32].copy_from_slice(&seed.0); @@ -84,6 +88,11 @@ impl Hasher for Blake3_192 { ByteDigest(result.as_bytes()[..24].try_into().unwrap()) } + fn merge_many(values: &[Self::Digest]) -> Self::Digest { + let result = blake3::hash(ByteDigest::digests_as_bytes(values)); + ByteDigest(result.as_bytes()[..24].try_into().unwrap()) + } + fn merge_with_int(seed: Self::Digest, value: u64) -> Self::Digest { let mut data = [0; 32]; data[..24].copy_from_slice(&seed.0); diff --git a/crypto/src/hash/blake/tests.rs b/crypto/src/hash/blake/tests.rs index 773e7410e..7018aee56 100644 --- a/crypto/src/hash/blake/tests.rs +++ b/crypto/src/hash/blake/tests.rs @@ -5,8 +5,10 @@ use math::{fields::f62::BaseElement, FieldElement}; use rand_utils::rand_array; +use utils::Deserializable; use super::{Blake3_256, ElementHasher, Hasher}; +use crate::hash::{Blake3_192, ByteDigest}; #[test] fn hash_padding() { @@ -29,3 +31,25 @@ fn hash_elements_padding() { let r2 = Blake3_256::hash_elements(&e2); assert_ne!(r1, r2); } + +#[test] +fn merge_vs_merge_many_256() { + let digest_0 = ByteDigest::read_from_bytes(&[1_u8; 32]).unwrap(); + let digest_1 = ByteDigest::read_from_bytes(&[2_u8; 32]).unwrap(); + + let r1 = Blake3_256::::merge(&[digest_0, digest_1]); + let r2 = Blake3_256::::merge_many(&[digest_0, digest_1]); + + assert_eq!(r1, r2) +} + +#[test] +fn merge_vs_merge_many_192() { + let digest_0 = ByteDigest::read_from_bytes(&[1_u8; 24]).unwrap(); + let digest_1 = ByteDigest::read_from_bytes(&[2_u8; 24]).unwrap(); + + let r1 = Blake3_192::::merge(&[digest_0, digest_1]); + let r2 = Blake3_192::::merge_many(&[digest_0, digest_1]); + + assert_eq!(r1, r2) +} diff --git a/crypto/src/hash/mod.rs b/crypto/src/hash/mod.rs index 6d68ac8ca..4bede6b8d 100644 --- a/crypto/src/hash/mod.rs +++ b/crypto/src/hash/mod.rs @@ -42,6 +42,9 @@ pub trait Hasher { /// Merkle trees. fn merge(values: &[Self::Digest; 2]) -> Self::Digest; + /// Returns a hash of many digests. + fn merge_many(values: &[Self::Digest]) -> Self::Digest; + /// Returns hash(`seed` || `value`). This method is intended for use in PRNG and PoW contexts. fn merge_with_int(seed: Self::Digest, value: u64) -> Self::Digest; } diff --git a/crypto/src/hash/rescue/rp62_248/mod.rs b/crypto/src/hash/rescue/rp62_248/mod.rs index aebcfbc3a..3c39155b6 100644 --- a/crypto/src/hash/rescue/rp62_248/mod.rs +++ b/crypto/src/hash/rescue/rp62_248/mod.rs @@ -165,6 +165,10 @@ impl Hasher for Rp62_248 { ElementDigest::new(state[..DIGEST_SIZE].try_into().unwrap()) } + fn merge_many(values: &[Self::Digest]) -> Self::Digest { + Self::hash_elements(ElementDigest::digests_as_elements(values)) + } + fn merge_with_int(seed: Self::Digest, value: u64) -> Self::Digest { // initialize the state as follows: // - seed is copied into the first 4 elements of the state. diff --git a/crypto/src/hash/rescue/rp62_248/tests.rs b/crypto/src/hash/rescue/rp62_248/tests.rs index 3f6960b98..ffee98602 100644 --- a/crypto/src/hash/rescue/rp62_248/tests.rs +++ b/crypto/src/hash/rescue/rp62_248/tests.rs @@ -83,6 +83,20 @@ fn hash_elements_vs_merge() { assert_eq!(m_result, h_result); } +#[test] +fn merge_vs_merge_many() { + let elements: [BaseElement; 8] = rand_array(); + + let digests: [ElementDigest; 2] = [ + ElementDigest::new(elements[..4].try_into().unwrap()), + ElementDigest::new(elements[4..].try_into().unwrap()), + ]; + + let m_result = Rp62_248::merge(&digests); + let h_result = Rp62_248::merge_many(&digests); + assert_eq!(m_result, h_result); +} + #[test] fn hash_elements_vs_merge_with_int() { let seed = ElementDigest::new(rand_array()); diff --git a/crypto/src/hash/rescue/rp64_256/mod.rs b/crypto/src/hash/rescue/rp64_256/mod.rs index b4f3e9d29..0d87de3f7 100644 --- a/crypto/src/hash/rescue/rp64_256/mod.rs +++ b/crypto/src/hash/rescue/rp64_256/mod.rs @@ -191,6 +191,10 @@ impl Hasher for Rp64_256 { ElementDigest::new(state[DIGEST_RANGE].try_into().unwrap()) } + fn merge_many(values: &[Self::Digest]) -> Self::Digest { + Self::hash_elements(ElementDigest::digests_as_elements(values)) + } + fn merge_with_int(seed: Self::Digest, value: u64) -> Self::Digest { // initialize the state as follows: // - seed is copied into the first 4 elements of the rate portion of the state. diff --git a/crypto/src/hash/rescue/rp64_256/tests.rs b/crypto/src/hash/rescue/rp64_256/tests.rs index bbd10adee..8d7d8f89d 100644 --- a/crypto/src/hash/rescue/rp64_256/tests.rs +++ b/crypto/src/hash/rescue/rp64_256/tests.rs @@ -118,6 +118,20 @@ fn hash_elements_vs_merge() { assert_eq!(m_result, h_result); } +#[test] +fn merge_vs_merge_many() { + let elements: [BaseElement; 8] = rand_array(); + + let digests: [ElementDigest; 2] = [ + ElementDigest::new(elements[..4].try_into().unwrap()), + ElementDigest::new(elements[4..].try_into().unwrap()), + ]; + + let m_result = Rp64_256::merge(&digests); + let h_result = Rp64_256::merge_many(&digests); + assert_eq!(m_result, h_result); +} + #[test] fn hash_elements_vs_merge_with_int() { let seed = ElementDigest::new(rand_array()); diff --git a/crypto/src/hash/rescue/rp64_256_jive/mod.rs b/crypto/src/hash/rescue/rp64_256_jive/mod.rs index 6c5c4411f..d369b0164 100644 --- a/crypto/src/hash/rescue/rp64_256_jive/mod.rs +++ b/crypto/src/hash/rescue/rp64_256_jive/mod.rs @@ -195,6 +195,10 @@ impl Hasher for RpJive64_256 { Self::apply_jive_summation(&initial_state, &state) } + fn merge_many(values: &[Self::Digest]) -> Self::Digest { + Self::hash_elements(ElementDigest::digests_as_elements(values)) + } + // We do not rely on the sponge construction to build our compression function. Instead, we use // the Jive compression mode designed in https://eprint.iacr.org/2022/840.pdf. fn merge_with_int(seed: Self::Digest, value: u64) -> Self::Digest { diff --git a/crypto/src/hash/sha/mod.rs b/crypto/src/hash/sha/mod.rs index 28da1ae1d..a0035cd3b 100644 --- a/crypto/src/hash/sha/mod.rs +++ b/crypto/src/hash/sha/mod.rs @@ -31,6 +31,10 @@ impl Hasher for Sha3_256 { ByteDigest(sha3::Sha3_256::digest(ByteDigest::digests_as_bytes(values)).into()) } + fn merge_many(values: &[Self::Digest]) -> Self::Digest { + ByteDigest(sha3::Sha3_256::digest(ByteDigest::digests_as_bytes(values)).into()) + } + fn merge_with_int(seed: Self::Digest, value: u64) -> Self::Digest { let mut data = [0; 40]; data[..32].copy_from_slice(&seed.0); diff --git a/examples/src/fibonacci/fib2/prover.rs b/examples/src/fibonacci/fib2/prover.rs index 9fb3dd500..99d48f004 100644 --- a/examples/src/fibonacci/fib2/prover.rs +++ b/examples/src/fibonacci/fib2/prover.rs @@ -5,8 +5,8 @@ use winterfell::{ crypto::MerkleTree, matrix::ColMatrix, AuxRandElements, ConstraintCompositionCoefficients, - DefaultConstraintEvaluator, DefaultTraceLde, StarkDomain, Trace, TraceInfo, TracePolyTable, - TraceTable, + DefaultConstraintEvaluator, DefaultTraceLde, PartitionOptions, StarkDomain, Trace, TraceInfo, + TracePolyTable, TraceTable, }; use super::{ @@ -77,8 +77,9 @@ where trace_info: &TraceInfo, main_trace: &ColMatrix, domain: &StarkDomain, + partition_option: PartitionOptions, ) -> (Self::TraceLde, TracePolyTable) { - DefaultTraceLde::new(trace_info, main_trace, domain) + DefaultTraceLde::new(trace_info, main_trace, domain, partition_option) } fn new_evaluator<'a, E: FieldElement>( diff --git a/examples/src/fibonacci/fib8/prover.rs b/examples/src/fibonacci/fib8/prover.rs index 425bfbd42..64182978c 100644 --- a/examples/src/fibonacci/fib8/prover.rs +++ b/examples/src/fibonacci/fib8/prover.rs @@ -5,8 +5,8 @@ use winterfell::{ crypto::MerkleTree, matrix::ColMatrix, AuxRandElements, ConstraintCompositionCoefficients, - DefaultConstraintEvaluator, DefaultTraceLde, StarkDomain, Trace, TraceInfo, TracePolyTable, - TraceTable, + DefaultConstraintEvaluator, DefaultTraceLde, PartitionOptions, StarkDomain, Trace, TraceInfo, + TracePolyTable, TraceTable, }; use super::{ @@ -92,8 +92,9 @@ where trace_info: &TraceInfo, main_trace: &ColMatrix, domain: &StarkDomain, + partition_option: PartitionOptions, ) -> (Self::TraceLde, TracePolyTable) { - DefaultTraceLde::new(trace_info, main_trace, domain) + DefaultTraceLde::new(trace_info, main_trace, domain, partition_option) } fn new_evaluator<'a, E: FieldElement>( diff --git a/examples/src/fibonacci/fib_small/prover.rs b/examples/src/fibonacci/fib_small/prover.rs index 53ba615da..553988064 100644 --- a/examples/src/fibonacci/fib_small/prover.rs +++ b/examples/src/fibonacci/fib_small/prover.rs @@ -4,8 +4,8 @@ // LICENSE file in the root directory of this source tree. use winterfell::{ crypto::MerkleTree, matrix::ColMatrix, AuxRandElements, ConstraintCompositionCoefficients, - DefaultConstraintEvaluator, DefaultTraceLde, StarkDomain, Trace, TraceInfo, TracePolyTable, - TraceTable, + DefaultConstraintEvaluator, DefaultTraceLde, PartitionOptions, StarkDomain, Trace, TraceInfo, + TracePolyTable, TraceTable, }; use super::{ @@ -82,8 +82,9 @@ where trace_info: &TraceInfo, main_trace: &ColMatrix, domain: &StarkDomain, + partition_option: PartitionOptions, ) -> (Self::TraceLde, TracePolyTable) { - DefaultTraceLde::new(trace_info, main_trace, domain) + DefaultTraceLde::new(trace_info, main_trace, domain, partition_option) } fn new_evaluator<'a, E: FieldElement>( diff --git a/examples/src/fibonacci/mulfib2/prover.rs b/examples/src/fibonacci/mulfib2/prover.rs index b1daba2fb..4c99187bf 100644 --- a/examples/src/fibonacci/mulfib2/prover.rs +++ b/examples/src/fibonacci/mulfib2/prover.rs @@ -5,8 +5,8 @@ use winterfell::{ crypto::MerkleTree, matrix::ColMatrix, AuxRandElements, ConstraintCompositionCoefficients, - DefaultConstraintEvaluator, DefaultTraceLde, StarkDomain, Trace, TraceInfo, TracePolyTable, - TraceTable, + DefaultConstraintEvaluator, DefaultTraceLde, PartitionOptions, StarkDomain, Trace, TraceInfo, + TracePolyTable, TraceTable, }; use super::{ @@ -73,8 +73,9 @@ where trace_info: &TraceInfo, main_trace: &ColMatrix, domain: &StarkDomain, + partition_option: PartitionOptions, ) -> (Self::TraceLde, TracePolyTable) { - DefaultTraceLde::new(trace_info, main_trace, domain) + DefaultTraceLde::new(trace_info, main_trace, domain, partition_option) } fn new_evaluator<'a, E: FieldElement>( diff --git a/examples/src/fibonacci/mulfib8/prover.rs b/examples/src/fibonacci/mulfib8/prover.rs index 20297d0e5..1fb58bd1a 100644 --- a/examples/src/fibonacci/mulfib8/prover.rs +++ b/examples/src/fibonacci/mulfib8/prover.rs @@ -5,8 +5,8 @@ use winterfell::{ crypto::MerkleTree, matrix::ColMatrix, AuxRandElements, ConstraintCompositionCoefficients, - DefaultConstraintEvaluator, DefaultTraceLde, StarkDomain, Trace, TraceInfo, TracePolyTable, - TraceTable, + DefaultConstraintEvaluator, DefaultTraceLde, PartitionOptions, StarkDomain, Trace, TraceInfo, + TracePolyTable, TraceTable, }; use super::{ @@ -85,8 +85,9 @@ where trace_info: &TraceInfo, main_trace: &ColMatrix, domain: &StarkDomain, + partition_option: PartitionOptions, ) -> (Self::TraceLde, TracePolyTable) { - DefaultTraceLde::new(trace_info, main_trace, domain) + DefaultTraceLde::new(trace_info, main_trace, domain, partition_option) } fn new_evaluator<'a, E: FieldElement>( diff --git a/examples/src/lamport/aggregate/prover.rs b/examples/src/lamport/aggregate/prover.rs index 51d8e9c30..3927a20e6 100644 --- a/examples/src/lamport/aggregate/prover.rs +++ b/examples/src/lamport/aggregate/prover.rs @@ -7,8 +7,8 @@ use winterfell::iterators::*; use winterfell::{ crypto::MerkleTree, matrix::ColMatrix, AuxRandElements, ConstraintCompositionCoefficients, - DefaultConstraintEvaluator, DefaultTraceLde, StarkDomain, TraceInfo, TracePolyTable, - TraceTable, + DefaultConstraintEvaluator, DefaultTraceLde, PartitionOptions, StarkDomain, TraceInfo, + TracePolyTable, TraceTable, }; use super::{ @@ -121,8 +121,9 @@ where trace_info: &TraceInfo, main_trace: &ColMatrix, domain: &StarkDomain, + partition_option: PartitionOptions, ) -> (Self::TraceLde, TracePolyTable) { - DefaultTraceLde::new(trace_info, main_trace, domain) + DefaultTraceLde::new(trace_info, main_trace, domain, partition_option) } fn new_evaluator<'a, E: FieldElement>( diff --git a/examples/src/lamport/threshold/prover.rs b/examples/src/lamport/threshold/prover.rs index f5c9c748b..87bd09bf6 100644 --- a/examples/src/lamport/threshold/prover.rs +++ b/examples/src/lamport/threshold/prover.rs @@ -9,8 +9,8 @@ use std::collections::HashMap; use winterfell::iterators::*; use winterfell::{ crypto::MerkleTree, matrix::ColMatrix, AuxRandElements, ConstraintCompositionCoefficients, - DefaultConstraintEvaluator, DefaultTraceLde, StarkDomain, TraceInfo, TracePolyTable, - TraceTable, + DefaultConstraintEvaluator, DefaultTraceLde, PartitionOptions, StarkDomain, TraceInfo, + TracePolyTable, TraceTable, }; use super::{ @@ -163,8 +163,9 @@ where trace_info: &TraceInfo, main_trace: &ColMatrix, domain: &StarkDomain, + partition_option: PartitionOptions, ) -> (Self::TraceLde, TracePolyTable) { - DefaultTraceLde::new(trace_info, main_trace, domain) + DefaultTraceLde::new(trace_info, main_trace, domain, partition_option) } fn new_evaluator<'a, E: FieldElement>( diff --git a/examples/src/merkle/prover.rs b/examples/src/merkle/prover.rs index db6d7f407..b1164ff83 100644 --- a/examples/src/merkle/prover.rs +++ b/examples/src/merkle/prover.rs @@ -5,8 +5,8 @@ use winterfell::{ crypto::MerkleTree, matrix::ColMatrix, AuxRandElements, ConstraintCompositionCoefficients, - DefaultConstraintEvaluator, DefaultTraceLde, StarkDomain, Trace, TraceInfo, TracePolyTable, - TraceTable, + DefaultConstraintEvaluator, DefaultTraceLde, PartitionOptions, StarkDomain, Trace, TraceInfo, + TracePolyTable, TraceTable, }; use super::{ @@ -128,8 +128,9 @@ where trace_info: &TraceInfo, main_trace: &ColMatrix, domain: &StarkDomain, + partition_option: PartitionOptions, ) -> (Self::TraceLde, TracePolyTable) { - DefaultTraceLde::new(trace_info, main_trace, domain) + DefaultTraceLde::new(trace_info, main_trace, domain, partition_option) } fn new_evaluator<'a, E: FieldElement>( diff --git a/examples/src/rescue/prover.rs b/examples/src/rescue/prover.rs index 050838af6..e8ca93757 100644 --- a/examples/src/rescue/prover.rs +++ b/examples/src/rescue/prover.rs @@ -5,8 +5,8 @@ use winterfell::{ crypto::MerkleTree, matrix::ColMatrix, AuxRandElements, ConstraintCompositionCoefficients, - DefaultConstraintEvaluator, DefaultTraceLde, StarkDomain, Trace, TraceInfo, TracePolyTable, - TraceTable, + DefaultConstraintEvaluator, DefaultTraceLde, PartitionOptions, StarkDomain, Trace, TraceInfo, + TracePolyTable, TraceTable, }; use super::{ @@ -95,8 +95,9 @@ where trace_info: &TraceInfo, main_trace: &ColMatrix, domain: &StarkDomain, + partition_option: PartitionOptions, ) -> (Self::TraceLde, TracePolyTable) { - DefaultTraceLde::new(trace_info, main_trace, domain) + DefaultTraceLde::new(trace_info, main_trace, domain, partition_option) } fn new_evaluator<'a, E: FieldElement>( diff --git a/examples/src/rescue_raps/prover.rs b/examples/src/rescue_raps/prover.rs index 7adee9bbb..b8b21b1f3 100644 --- a/examples/src/rescue_raps/prover.rs +++ b/examples/src/rescue_raps/prover.rs @@ -6,7 +6,8 @@ use core_utils::uninit_vector; use winterfell::{ crypto::MerkleTree, matrix::ColMatrix, AuxRandElements, ConstraintCompositionCoefficients, - DefaultConstraintEvaluator, DefaultTraceLde, StarkDomain, Trace, TraceInfo, TracePolyTable, + DefaultConstraintEvaluator, DefaultTraceLde, PartitionOptions, StarkDomain, Trace, TraceInfo, + TracePolyTable, }; use super::{ @@ -126,8 +127,9 @@ where trace_info: &TraceInfo, main_trace: &ColMatrix, domain: &StarkDomain, + partition_option: PartitionOptions, ) -> (Self::TraceLde, TracePolyTable) { - DefaultTraceLde::new(trace_info, main_trace, domain) + DefaultTraceLde::new(trace_info, main_trace, domain, partition_option) } fn new_evaluator<'a, E: FieldElement>( diff --git a/examples/src/utils/rescue.rs b/examples/src/utils/rescue.rs index ab9e6a79b..33ca425ca 100644 --- a/examples/src/utils/rescue.rs +++ b/examples/src/utils/rescue.rs @@ -117,6 +117,10 @@ impl Hasher for Rescue128 { Self::digest(Hash::hashes_as_elements(values)) } + fn merge_many(_values: &[Self::Digest]) -> Self::Digest { + unimplemented!("not implemented") + } + fn merge_with_int(_seed: Self::Digest, _value: u64) -> Self::Digest { unimplemented!("not implemented") } diff --git a/examples/src/vdf/exempt/prover.rs b/examples/src/vdf/exempt/prover.rs index cc5d3e8e8..16a7b8169 100644 --- a/examples/src/vdf/exempt/prover.rs +++ b/examples/src/vdf/exempt/prover.rs @@ -5,8 +5,8 @@ use winterfell::{ crypto::MerkleTree, matrix::ColMatrix, AuxRandElements, ConstraintCompositionCoefficients, - DefaultConstraintEvaluator, DefaultTraceLde, StarkDomain, Trace, TraceInfo, TracePolyTable, - TraceTable, + DefaultConstraintEvaluator, DefaultTraceLde, PartitionOptions, StarkDomain, Trace, TraceInfo, + TracePolyTable, TraceTable, }; use super::{ @@ -78,8 +78,9 @@ where trace_info: &TraceInfo, main_trace: &ColMatrix, domain: &StarkDomain, + partition_option: PartitionOptions, ) -> (Self::TraceLde, TracePolyTable) { - DefaultTraceLde::new(trace_info, main_trace, domain) + DefaultTraceLde::new(trace_info, main_trace, domain, partition_option) } fn new_evaluator<'a, E: FieldElement>( diff --git a/examples/src/vdf/regular/prover.rs b/examples/src/vdf/regular/prover.rs index c880611ff..20bdf7874 100644 --- a/examples/src/vdf/regular/prover.rs +++ b/examples/src/vdf/regular/prover.rs @@ -5,8 +5,8 @@ use winterfell::{ crypto::MerkleTree, matrix::ColMatrix, AuxRandElements, ConstraintCompositionCoefficients, - DefaultConstraintEvaluator, DefaultTraceLde, StarkDomain, Trace, TraceInfo, TracePolyTable, - TraceTable, + DefaultConstraintEvaluator, DefaultTraceLde, PartitionOptions, StarkDomain, Trace, TraceInfo, + TracePolyTable, TraceTable, }; use super::{ @@ -73,8 +73,9 @@ where trace_info: &TraceInfo, main_trace: &ColMatrix, domain: &StarkDomain, + partition_option: PartitionOptions, ) -> (Self::TraceLde, TracePolyTable) { - DefaultTraceLde::new(trace_info, main_trace, domain) + DefaultTraceLde::new(trace_info, main_trace, domain, partition_option) } fn new_evaluator<'a, E: FieldElement>( diff --git a/prover/benches/lagrange_kernel.rs b/prover/benches/lagrange_kernel.rs index 7ee8ab3c3..d6ab6a5bc 100644 --- a/prover/benches/lagrange_kernel.rs +++ b/prover/benches/lagrange_kernel.rs @@ -7,8 +7,8 @@ use std::time::Duration; use air::{ Air, AirContext, Assertion, AuxRandElements, ConstraintCompositionCoefficients, - EvaluationFrame, FieldExtension, GkrRandElements, LagrangeKernelRandElements, ProofOptions, - TraceInfo, TransitionConstraintDegree, + EvaluationFrame, FieldExtension, GkrRandElements, LagrangeKernelRandElements, PartitionOptions, + ProofOptions, TraceInfo, TransitionConstraintDegree, }; use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion}; use crypto::{hashers::Blake3_256, DefaultRandomCoin, MerkleTree, RandomCoin}; @@ -202,11 +202,12 @@ impl Prover for LagrangeProver { trace_info: &TraceInfo, main_trace: &ColMatrix, domain: &StarkDomain, + partition_option: PartitionOptions, ) -> (Self::TraceLde, TracePolyTable) where E: math::FieldElement, { - DefaultTraceLde::new(trace_info, main_trace, domain) + DefaultTraceLde::new(trace_info, main_trace, domain, partition_option) } fn new_evaluator<'a, E>( diff --git a/prover/src/lib.rs b/prover/src/lib.rs index ac0e82be2..035d6c655 100644 --- a/prover/src/lib.rs +++ b/prover/src/lib.rs @@ -48,7 +48,7 @@ pub use air::{ EvaluationFrame, FieldExtension, LagrangeKernelRandElements, ProofOptions, TraceInfo, TransitionConstraintDegree, }; -use air::{AuxRandElements, GkrRandElements}; +use air::{AuxRandElements, GkrRandElements, PartitionOptions}; pub use crypto; use crypto::{ElementHasher, RandomCoin, VectorCommitment}; use fri::FriProver; @@ -182,6 +182,7 @@ pub trait Prover { trace_info: &TraceInfo, main_trace: &ColMatrix, domain: &StarkDomain, + partition_option: PartitionOptions, ) -> (Self::TraceLde, TracePolyTable) where E: FieldElement; @@ -554,7 +555,11 @@ pub trait Prover { log_domain_size = domain_size.ilog2() ) .in_scope(|| { - let commitment = composed_evaluations.commit_to_rows::(); + let commitment = composed_evaluations.commit_to_rows::( + self.options() + .partition_options() + .partition_size::(num_constraint_composition_columns), + ); ConstraintCommitment::new(composed_evaluations, commitment) }); @@ -574,8 +579,12 @@ pub trait Prover { E: FieldElement, { // extend the main execution trace and commit to the extended trace - let (trace_lde, trace_polys) = - maybe_await!(self.new_trace_lde(trace.info(), trace.main_segment(), domain)); + let (trace_lde, trace_polys) = maybe_await!(self.new_trace_lde( + trace.info(), + trace.main_segment(), + domain, + self.options().partition_options(), + )); // get the commitment to the main trace segment LDE let main_trace_commitment = trace_lde.get_main_trace_commitment(); diff --git a/prover/src/matrix/row_matrix.rs b/prover/src/matrix/row_matrix.rs index f42ca0e7a..85b43122e 100644 --- a/prover/src/matrix/row_matrix.rs +++ b/prover/src/matrix/row_matrix.rs @@ -180,7 +180,7 @@ impl RowMatrix { /// * A vector commitment is computed for the resulting vector using the specified vector /// commitment scheme. /// * The resulting vector commitment is returned as the commitment to the entire matrix. - pub fn commit_to_rows(&self) -> V + pub fn commit_to_rows(&self, partition_size: usize) -> V where H: ElementHasher, V: VectorCommitment, @@ -188,16 +188,34 @@ impl RowMatrix { // allocate vector to store row hashes let mut row_hashes = unsafe { uninit_vector::(self.num_rows()) }; - // iterate though matrix rows, hashing each row - batch_iter_mut!( - &mut row_hashes, - 128, // min batch size - |batch: &mut [H::Digest], batch_offset: usize| { - for (i, row_hash) in batch.iter_mut().enumerate() { - *row_hash = H::hash_elements(self.row(batch_offset + i)); + if partition_size == self.num_cols() * E::EXTENSION_DEGREE { + // iterate though matrix rows, hashing each row + batch_iter_mut!( + &mut row_hashes, + 128, // min batch size + |batch: &mut [H::Digest], batch_offset: usize| { + for (i, row_hash) in batch.iter_mut().enumerate() { + *row_hash = H::hash_elements(self.row(batch_offset + i)); + } } - } - ); + ); + } else { + // iterate though matrix rows, hashing each row + batch_iter_mut!( + &mut row_hashes, + 128, // min batch size + |batch: &mut [H::Digest], batch_offset: usize| { + let mut buffer = vec![H::Digest::default(); partition_size]; + for (i, row_hash) in batch.iter_mut().enumerate() { + self.row(batch_offset + i) + .chunks(partition_size) + .zip(buffer.iter_mut()) + .for_each(|(chunk, buf)| *buf = H::hash_elements(chunk)); + *row_hash = H::merge_many(&buffer); + } + } + ); + } // build the vector commitment to the hashed rows V::new(row_hashes).expect("failed to construct trace vector commitment") diff --git a/prover/src/trace/trace_lde/default/mod.rs b/prover/src/trace/trace_lde/default/mod.rs index e06839d53..26b5e3916 100644 --- a/prover/src/trace/trace_lde/default/mod.rs +++ b/prover/src/trace/trace_lde/default/mod.rs @@ -6,7 +6,7 @@ use alloc::vec::Vec; use core::marker::PhantomData; -use air::{proof::Queries, LagrangeKernelEvaluationFrame, TraceInfo}; +use air::{proof::Queries, LagrangeKernelEvaluationFrame, PartitionOptions, TraceInfo}; use crypto::VectorCommitment; use tracing::info_span; @@ -43,6 +43,7 @@ pub struct DefaultTraceLde< aux_segment_oracles: Option, blowup: usize, trace_info: TraceInfo, + partition_option: PartitionOptions, _h: PhantomData, } @@ -63,10 +64,15 @@ where trace_info: &TraceInfo, main_trace: &ColMatrix, domain: &StarkDomain, + partition_option: PartitionOptions, ) -> (Self, TracePolyTable) { // extend the main execution trace and build a commitment to the extended trace let (main_segment_lde, main_segment_vector_com, main_segment_polys) = - build_trace_commitment::(main_trace, domain); + build_trace_commitment::( + main_trace, + domain, + partition_option.partition_size::(main_trace.num_cols()), + ); let trace_poly_table = TracePolyTable::new(main_segment_polys); let trace_lde = DefaultTraceLde { @@ -76,6 +82,7 @@ where aux_segment_oracles: None, blowup: domain.trace_to_lde_blowup(), trace_info: trace_info.clone(), + partition_option, _h: PhantomData, }; @@ -122,7 +129,9 @@ where /// Takes auxiliary trace segment columns as input, interpolates them into polynomials in /// coefficient form, evaluates the polynomials over the LDE domain, and commits to the - /// polynomial evaluations. + /// polynomial evaluations. Depending on whether `num_partitions` is equal to `1` or is + /// greater than `1`, committing to the polynomial evaluations row-wise is done either + /// in one go in the former or in `num_partition` steps which are then combined in the latter. /// /// Returns a tuple containing the column polynomials in coefficient from and the commitment /// to the polynomial evaluations over the LDE domain. @@ -139,7 +148,11 @@ where ) -> (ColMatrix, H::Digest) { // extend the auxiliary trace segment and build a commitment to the extended trace let (aux_segment_lde, aux_segment_oracles, aux_segment_polys) = - build_trace_commitment::(aux_trace, domain); + build_trace_commitment::( + aux_trace, + domain, + self.partition_option.partition_size::(aux_trace.num_cols()), + ); // check errors assert!( @@ -263,6 +276,7 @@ where fn build_trace_commitment( trace: &ColMatrix, domain: &StarkDomain, + partition_size: usize, ) -> (RowMatrix, V, ColMatrix) where E: FieldElement, @@ -292,7 +306,7 @@ where // build trace commitment let commitment_domain_size = trace_lde.num_rows(); let trace_vector_com = info_span!("compute_execution_trace_commitment", commitment_domain_size) - .in_scope(|| trace_lde.commit_to_rows::()); + .in_scope(|| trace_lde.commit_to_rows::(partition_size)); assert_eq!(trace_vector_com.domain_len(), commitment_domain_size); (trace_lde, trace_vector_com, trace_polys) diff --git a/prover/src/trace/trace_lde/default/tests.rs b/prover/src/trace/trace_lde/default/tests.rs index c06cc2e60..734accf68 100644 --- a/prover/src/trace/trace_lde/default/tests.rs +++ b/prover/src/trace/trace_lde/default/tests.rs @@ -5,6 +5,7 @@ use alloc::vec::Vec; +use air::PartitionOptions; use crypto::{hashers::Blake3_256, ElementHasher, MerkleTree}; use math::{ fields::f128::BaseElement, get_power_series, get_power_series_with_offset, polynom, @@ -25,12 +26,14 @@ fn extend_trace_table() { let air = MockAir::with_trace_length(trace_length); let trace = build_fib_trace(trace_length * 2); let domain = StarkDomain::new(&air); + let partition_option = PartitionOptions::default(); // build the trace polynomials, extended trace, and commitment using the default TraceLde impl let (trace_lde, trace_polys) = DefaultTraceLde::>::new( trace.info(), trace.main_segment(), &domain, + partition_option, ); // check the width and length of the extended trace @@ -75,12 +78,14 @@ fn commit_trace_table() { let air = MockAir::with_trace_length(trace_length); let trace = build_fib_trace(trace_length * 2); let domain = StarkDomain::new(&air); + let partition_option = PartitionOptions::default(); // build the trace polynomials, extended trace, and commitment using the default TraceLde impl let (trace_lde, _) = DefaultTraceLde::>::new( trace.info(), trace.main_segment(), &domain, + partition_option, ); // build commitment, using a Merkle tree, to the trace rows diff --git a/verifier/src/channel.rs b/verifier/src/channel.rs index c84f4ec2a..9d7dbc426 100644 --- a/verifier/src/channel.rs +++ b/verifier/src/channel.rs @@ -35,6 +35,10 @@ pub struct VerifierChannel< // constraint queries constraint_commitment: H::Digest, constraint_queries: Option>, + // partition sizes for the rows of main, auxiliary and constraint traces rows + partition_size_main: usize, + partition_size_aux: usize, + partition_size_constraint: usize, // FRI proof fri_commitments: Option>, fri_layer_proofs: Vec, @@ -85,6 +89,7 @@ where let aux_trace_width = air.trace_info().aux_segment_width(); let lde_domain_size = air.lde_domain_size(); let fri_options = air.options().to_fri_options(); + let partition_options = air.options().partition_options(); // --- parse commitments ------------------------------------------------------------------ let (trace_commitments, constraint_commitment, fri_commitments) = commitments @@ -114,6 +119,14 @@ where .parse(main_trace_width, aux_trace_width, constraint_frame_width) .map_err(|err| VerifierError::ProofDeserializationError(err.to_string()))?; + // --- compute the partition size for each trace ------------------------------------------ + let partition_size_main = partition_options + .partition_size::(air.context().trace_info().main_trace_width()); + let partition_size_aux = + partition_options.partition_size::(air.context().trace_info().aux_segment_width()); + let partition_size_constraint = partition_options + .partition_size::(air.context().num_constraint_composition_columns()); + Ok(VerifierChannel { // trace queries trace_commitments, @@ -121,6 +134,10 @@ where // constraint queries constraint_commitment, constraint_queries: Some(constraint_queries), + // num partitions used in commitment + partition_size_main, + partition_size_aux, + partition_size_constraint, // FRI proof fri_commitments: Some(fri_commitments), fri_layer_proofs, @@ -191,9 +208,12 @@ where let queries = self.trace_queries.take().expect("already read"); // make sure the states included in the proof correspond to the trace commitment + let items: Vec = queries + .main_states + .rows() + .map(|row| hash_row::(row, self.partition_size_main)) + .collect(); - let items: Vec = - queries.main_states.rows().map(|row| H::hash_elements(row)).collect(); >::verify_many( self.trace_commitments[0], positions, @@ -203,8 +223,11 @@ where .map_err(|_| VerifierError::TraceQueryDoesNotMatchCommitment)?; if let Some(ref aux_states) = queries.aux_states { - let items: Vec = - aux_states.rows().map(|row| H::hash_elements(row)).collect(); + let items: Vec = aux_states + .rows() + .map(|row| hash_row::(row, self.partition_size_aux)) + .collect(); + >::verify_many( self.trace_commitments[1], positions, @@ -225,8 +248,13 @@ where positions: &[usize], ) -> Result, VerifierError> { let queries = self.constraint_queries.take().expect("already read"); - let items: Vec = - queries.evaluations.rows().map(|row| H::hash_elements(row)).collect(); + + let items: Vec = queries + .evaluations + .rows() + .map(|row| hash_row::(row, self.partition_size_constraint)) + .collect(); + >::verify_many( self.constraint_commitment, positions, @@ -404,3 +432,24 @@ where }) } } + +// HELPER +// ================================================================================================ + +/// Hashes a row of a trace in batches where each batch is of size at most `partition_size`. +fn hash_row(row: &[E], partition_size: usize) -> H::Digest +where + E: FieldElement, + H: ElementHasher, +{ + if partition_size == row.len() * E::EXTENSION_DEGREE { + H::hash_elements(row) + } else { + let mut buffer = vec![H::Digest::default(); partition_size]; + + row.chunks(partition_size) + .zip(buffer.iter_mut()) + .for_each(|(chunk, buf)| *buf = H::hash_elements(chunk)); + H::merge_many(&buffer) + } +} diff --git a/winterfell/src/lib.rs b/winterfell/src/lib.rs index 86c5e0345..9074f6eef 100644 --- a/winterfell/src/lib.rs +++ b/winterfell/src/lib.rs @@ -266,7 +266,7 @@ //! //! # use winterfell::{ //! # Air, AirContext, Assertion, AuxRandElements, ByteWriter, DefaultConstraintEvaluator, -//! # EvaluationFrame, TraceInfo, TransitionConstraintDegree, +//! # EvaluationFrame, PartitionOptions, TraceInfo, TransitionConstraintDegree, //! # }; //! # //! # pub struct PublicInputs { @@ -371,8 +371,9 @@ //! trace_info: &TraceInfo, //! main_trace: &ColMatrix, //! domain: &StarkDomain, +//! partition_option: PartitionOptions, //! ) -> (Self::TraceLde, TracePolyTable) { -//! DefaultTraceLde::new(trace_info, main_trace, domain) +//! DefaultTraceLde::new(trace_info, main_trace, domain, partition_option) //! } //! //! fn new_evaluator<'a, E: FieldElement>( @@ -400,7 +401,7 @@ //! # matrix::ColMatrix, //! # Air, AirContext, Assertion, AuxRandElements, ByteWriter, DefaultConstraintEvaluator, //! # DefaultTraceLde, EvaluationFrame, TraceInfo, -//! # TransitionConstraintDegree, TraceTable, FieldExtension, Prover, +//! # TransitionConstraintDegree, TraceTable, FieldExtension, PartitionOptions, Prover, //! # ProofOptions, StarkDomain, Proof, Trace, TracePolyTable, //! # }; //! # @@ -514,8 +515,9 @@ //! # trace_info: &TraceInfo, //! # main_trace: &ColMatrix, //! # domain: &StarkDomain, +//! # partition_option: PartitionOptions, //! # ) -> (Self::TraceLde, TracePolyTable) { -//! # DefaultTraceLde::new(trace_info, main_trace, domain) +//! # DefaultTraceLde::new(trace_info, main_trace, domain, partition_option) //! # } //! # //! # fn new_evaluator<'a, E: FieldElement>( @@ -594,7 +596,7 @@ #[cfg(test)] extern crate std; -pub use air::{AuxRandElements, GkrVerifier}; +pub use air::{AuxRandElements, GkrVerifier, PartitionOptions}; pub use prover::{ crypto, iterators, math, matrix, Air, AirContext, Assertion, AuxTraceWithMetadata, BoundaryConstraint, BoundaryConstraintGroup, CompositionPolyTrace, diff --git a/winterfell/src/tests.rs b/winterfell/src/tests.rs index 3757e2010..3fb0c5197 100644 --- a/winterfell/src/tests.rs +++ b/winterfell/src/tests.rs @@ -234,11 +234,12 @@ impl Prover for LagrangeComplexProver { trace_info: &TraceInfo, main_trace: &ColMatrix, domain: &StarkDomain, + partition_option: PartitionOptions, ) -> (Self::TraceLde, TracePolyTable) where E: math::FieldElement, { - DefaultTraceLde::new(trace_info, main_trace, domain) + DefaultTraceLde::new(trace_info, main_trace, domain, partition_option) } fn new_evaluator<'a, E>(