Skip to main content

stwo_gpu/core/pcs/
quotients.rs

1use core::ops::Add;
2
3use itertools::{izip, zip_eq, Itertools};
4use num_traits::{One, Zero};
5use serde::{Deserialize, Serialize};
6use std_shims::Vec;
7
8use super::TreeVec;
9use crate::core::circle::CirclePoint;
10use crate::core::constraints::complex_conjugate_line_coeffs;
11use crate::core::fields::cm31::CM31;
12use crate::core::fields::m31::{BaseField, M31};
13use crate::core::fields::qm31::SecureField;
14use crate::core::fields::FieldExpOps;
15use crate::core::fri::{FriProof, FriProofAux};
16use crate::core::pcs::PcsConfig;
17use crate::core::poly::circle::CanonicCoset;
18use crate::core::utils::bit_reverse_index;
19use crate::core::vcs_lifted::merkle_hasher::MerkleHasherLifted;
20use crate::core::vcs_lifted::verifier::{MerkleDecommitmentLifted, MerkleDecommitmentLiftedAux};
21use crate::core::verifier::VerificationError;
22use crate::core::ColumnVec;
23// Used for no_std support.
24pub type IndexMap<K, V> = indexmap::IndexMap<K, V, core::hash::BuildHasherDefault<fnv::FnvHasher>>;
25
26#[derive(Clone, Debug, Serialize, Deserialize)]
27pub struct CommitmentSchemeProof<H: MerkleHasherLifted> {
28    pub config: PcsConfig,
29    pub commitments: TreeVec<H::Hash>,
30    pub sampled_values: TreeVec<ColumnVec<Vec<SecureField>>>,
31    pub decommitments: TreeVec<MerkleDecommitmentLifted<H>>,
32    pub queried_values: TreeVec<ColumnVec<Vec<BaseField>>>,
33    pub proof_of_work: u64,
34    pub fri_proof: FriProof<H>,
35}
36
37/// Auxiliary data for a [CommitmentSchemeProof].
38#[derive(Clone, Debug, Serialize, Deserialize)]
39pub struct CommitmentSchemeProofAux<H: MerkleHasherLifted> {
40    /// The indices of the queries in the ordered they were sampled, before sorting and
41    /// deduplication.
42    pub unsorted_query_locations: Vec<usize>,
43    /// For each trace, the Merkle decommitment auxiliary data.
44    pub trace_decommitment: TreeVec<MerkleDecommitmentLiftedAux<H>>,
45    /// The FRI auxiliary data.
46    pub fri: FriProofAux<H>,
47}
48
49pub struct ExtendedCommitmentSchemeProof<H: MerkleHasherLifted> {
50    pub proof: CommitmentSchemeProof<H>,
51    pub aux: CommitmentSchemeProofAux<H>,
52}
53
54/// A batch of column samplings at a point.
55pub struct ColumnSampleBatch {
56    /// The point at which the columns are sampled.
57    pub point: CirclePoint<SecureField>,
58    /// The sampled column indices, their values at the point, and the random coefficient power
59    /// corresponding to the fri quotient associated with (point, column_index, value).
60    pub cols_vals_randpows: Vec<NumeratorData>,
61}
62
63/// Helper struct used only in `ColumnSampleBatch`. For a sample point z in a `ColumnSampleBatch`,
64/// this struct contains the index of a column with such sample, the value of the column poly at z,
65/// and the random coefficient power corresponding to the fri quotient associated with (z,
66/// column_index, value).
67pub struct NumeratorData {
68    pub column_index: usize,
69    pub sample_value: SecureField,
70    pub random_coeff: SecureField,
71}
72
73impl ColumnSampleBatch {
74    /// Groups column samples by sampled point.
75    /// # Arguments
76    /// samples: For each column, a vector of samples.
77    pub fn new_vec(samples_with_rand: &[&Vec<(PointSample, SecureField)>]) -> Vec<Self> {
78        // Group samples by point, and create a ColumnSampleBatch for each point.
79        // This should keep a stable ordering.
80        let mut grouped_samples = IndexMap::default();
81        for (column_index, samples) in samples_with_rand.iter().enumerate() {
82            for (sample, rand_pow) in samples.iter() {
83                grouped_samples
84                    .entry(sample.point)
85                    .or_insert_with(Vec::new)
86                    .push(NumeratorData {
87                        column_index,
88                        sample_value: sample.value,
89                        random_coeff: *rand_pow,
90                    });
91            }
92        }
93        grouped_samples
94            .into_iter()
95            .map(|(point, cols_vals_randpows)| ColumnSampleBatch {
96                point,
97                cols_vals_randpows,
98            })
99            .collect()
100    }
101}
102
103#[derive(Clone)]
104pub struct PointSample {
105    pub point: CirclePoint<SecureField>,
106    pub value: SecureField,
107}
108
109/// For each query position, corresponding to a domain point `p`, compute the FRI quotients
110/// ```plain
111///     ∑ ∑ α^{k(i, z)} * (c(i, z) * f̃ᵢ(p) - b(i, z) - a(i, z)) / line(z,conj(z))(p)
112/// ```
113/// where:
114/// * the outer sum is over the set of sample points `z`,
115/// * the inner sum is over the set of columns (corresponding to index `i`),
116/// * line(z,conj(z))(p) is the equation of the line through (z, conj(z)) evaluated at `p`
117/// * f̃ᵢ is the lift of the trace poly fᵢ to the domain of maximal log size.
118/// * (a(i, z), b(i, z), c(i, z)) are the coefficients of the line equation `cY - aX - b` through
119///   (z.y, f̃ᵢ(z)), (conj(z.y), conj(f̃ᵢ(z)).
120pub fn fri_answers(
121    column_log_sizes: TreeVec<Vec<u32>>,
122    samples: TreeVec<Vec<Vec<PointSample>>>,
123    random_coeff: SecureField,
124    query_positions: &[usize],
125    queried_values: TreeVec<ColumnVec<Vec<BaseField>>>,
126    lifting_log_size: u32,
127) -> Result<Vec<SecureField>, VerificationError> {
128    let queried_values = queried_values.flatten();
129    assert!(queried_values
130        .iter()
131        .all(|queries_per_col| queries_per_col.len() == query_positions.len()));
132    let samples_with_randomness = build_samples_with_randomness_and_periodicity(
133        &samples,
134        column_log_sizes
135            .0
136            .into_iter()
137            .map(|x| x.into_iter())
138            .collect(),
139        lifting_log_size,
140        random_coeff,
141    );
142    let sample_batches =
143        ColumnSampleBatch::new_vec(&samples_with_randomness.iter().flatten().collect::<Vec<_>>());
144    let lifting_domain = CanonicCoset::new(lifting_log_size).circle_domain();
145    // Compute the quotient constants for all batches.
146    let quotient_constants = quotient_constants(&sample_batches);
147    let mut res = Vec::with_capacity(query_positions.len());
148    for (idx, position) in query_positions.iter().enumerate() {
149        let queried_values_at_row = queried_values.iter().map(|col| col[idx]).collect_vec();
150        let domain_point = lifting_domain.at(bit_reverse_index(*position, lifting_log_size));
151
152        res.push(accumulate_row_quotients(
153            &sample_batches,
154            &queried_values_at_row,
155            &quotient_constants,
156            domain_point,
157        ));
158    }
159    Ok(res)
160}
161
162pub fn accumulate_row_quotients(
163    sample_batches: &[ColumnSampleBatch],
164    queried_values_at_row: &[BaseField],
165    quotient_constants: &QuotientConstants,
166    domain_point: CirclePoint<BaseField>,
167) -> SecureField {
168    let sample_points = sample_batches.iter().map(|b| b.point).collect_vec();
169    let denominator_inverses = denominator_inverses(&sample_points, domain_point);
170    let mut row_accumulator = SecureField::zero();
171    for (sample_batch, line_coeffs, denominator_inverse) in izip!(
172        sample_batches,
173        &quotient_constants.line_coeffs,
174        denominator_inverses
175    ) {
176        let mut numerator = SecureField::zero();
177        for (NumeratorData { column_index, .. }, (a, b, c)) in
178            zip_eq(&sample_batch.cols_vals_randpows, line_coeffs)
179        {
180            let value = queried_values_at_row[*column_index] * *c;
181            // The numerator is a line equation passing through
182            //   (sample_point.y, sample_value), (conj(sample_point), conj(sample_value))
183            // evaluated at (domain_point.y, value).
184            // When substituting a polynomial in this line equation, we get a polynomial with a root
185            // at sample_point and conj(sample_point) if the original polynomial had the values
186            // sample_value and conj(sample_value) at these points.
187            let linear_term = *a * domain_point.y + *b;
188            numerator += value - linear_term;
189        }
190        // Note that `denominator_inverse` is an element of CM31 (see the docs and comments in the
191        // function `denominator_inverses`).
192        row_accumulator += numerator.mul_cm31(denominator_inverse);
193    }
194    row_accumulator
195}
196
197/// Computes the sum
198///     ∑ α^{k_i} * (cᵢ * f̃ᵢ(p) - bᵢ)
199/// where:
200/// * i is an index into `queried_values_at_row` that runs over the columns involved in the batch.
201/// * f̃ᵢ(p) is `queried_values_at_row[i]`.
202pub fn accumulate_row_partial_numerators(
203    batch: &ColumnSampleBatch,
204    queried_values_at_row: &[BaseField],
205    coeffs: &Vec<(SecureField, SecureField, SecureField)>,
206) -> SecureField {
207    let mut numerator = SecureField::zero();
208    for (NumeratorData { column_index, .. }, (_, b, c)) in zip_eq(&batch.cols_vals_randpows, coeffs)
209    {
210        let value = queried_values_at_row[*column_index] * *c;
211        numerator += value - *b;
212    }
213    numerator
214}
215
216/// Precomputes the complex conjugate line coefficients for each column in each sample batch.
217///
218/// For the `i`-th numerator term `curr_coeff_power * alpha^i * (c * F(p) - (a * p.y + b))`,
219/// we precompute and return the constants: (`curr_coeff_power * alpha^i * a`, `curr_coeff_power *
220/// alpha^i * b`, `curr_coeff_power * alpha^i * c`). The index `i` is zero-based and runs
221/// monotonically across all sample batches (i.e. the index of the `m`-th column in the `n`-th batch
222/// is `m + Σ len(batch_k)`, for `k < n`).
223pub fn column_line_coeffs(
224    sample_batches: &[ColumnSampleBatch],
225) -> Vec<Vec<(SecureField, SecureField, SecureField)>> {
226    sample_batches
227        .iter()
228        .map(|sample_batch| {
229            sample_batch
230                .cols_vals_randpows
231                .iter()
232                .map(
233                    |NumeratorData {
234                         column_index: _,
235                         sample_value,
236                         random_coeff,
237                     }| {
238                        let sample = PointSample {
239                            point: sample_batch.point,
240                            value: *sample_value,
241                        };
242                        complex_conjugate_line_coeffs(&sample, *random_coeff)
243                    },
244                )
245                .collect()
246        })
247        .collect()
248}
249
250/// For each sample point P, computes the equation of a line passing through P = (pₓ, pᵧ) ∈
251/// QM31 x QM31 and its conjugate P̄ = (p̄ₓ, p̄ᵧ), where the conjugate of an element of QM31 is with
252/// respect to CM31. Then evaluates the line equation at `domain_point`.
253pub fn denominator_inverses(
254    sample_points: &[CirclePoint<SecureField>],
255    domain_point: CirclePoint<M31>,
256) -> Vec<CM31> {
257    let mut denominators = Vec::new();
258
259    // To find the equation of the line through P and P̄: a point Q = (qₓ, qᵧ) is on the line iff P -
260    // Q is parallel to P - P̄ = (pₓ - p̄ₓ, pᵧ - p̄ᵧ), which is a multiple of (Im(pₓ), Im(pᵧ)).
261    // We have P - Q  = ((Re(pₓ) - qₓ) + u * Im(pₓ), (Re(pᵧ) - qᵧ) + u * Im(pᵧ)). The parallelism
262    // check reduces to
263    //      (Re(pₓ) - qₓ) * Im(pᵧ) - (Re(pᵧ) - qᵧ) * Im(pₓ) = 0.
264    // Note that this expression, evaluated at an arbitrary Q with M31 coordinates, is an element of
265    // CM31.
266    for sample_point in sample_points {
267        // Extract Re(pₓ), Re(pᵧ), Im(pₓ), Im(pᵧ).
268        let prx = sample_point.x.0;
269        let pry = sample_point.y.0;
270        let pix = sample_point.x.1;
271        let piy = sample_point.y.1;
272        denominators.push((prx - domain_point.x) * piy - (pry - domain_point.y) * pix);
273    }
274
275    CM31::batch_inverse(&denominators)
276}
277
278pub fn quotient_constants(sample_batches: &[ColumnSampleBatch]) -> QuotientConstants {
279    QuotientConstants {
280        line_coeffs: column_line_coeffs(sample_batches),
281    }
282}
283
284/// Holds the precomputed constant values used in each quotient evaluation.
285pub struct QuotientConstants {
286    /// The line coefficients for each quotient numerator term. For more details see
287    /// [self::column_line_coeffs].
288    pub line_coeffs: Vec<Vec<(SecureField, SecureField, SecureField)>>,
289}
290
291pub fn build_samples_with_randomness_and_periodicity(
292    samples: &TreeVec<Vec<Vec<PointSample>>>,
293    column_log_sizes: Vec<impl Iterator<Item = u32>>,
294    lifting_log_size: u32,
295    random_coeff: SecureField,
296) -> TreeVec<Vec<Vec<(PointSample, SecureField)>>> {
297    let mut random_pows = (0..).scan(SecureField::one(), |acc, _| {
298        let curr = *acc;
299        *acc *= random_coeff;
300        Some(curr)
301    });
302    let mut res: Vec<Vec<Vec<(PointSample, SecureField)>>> = Vec::new();
303    let lifting_domain_generator = CanonicCoset::new(lifting_log_size).step();
304    for (samples_per_tree, sizes_per_tree) in samples.iter().zip(column_log_sizes.into_iter()) {
305        let samples_with_randomness_and_periodicity = samples_per_tree
306            .iter()
307            .zip(sizes_per_tree)
308            .map(|(samples_per_cols, log_size)| {
309                if samples_per_cols.is_empty() {
310                    return Vec::new();
311                }
312                let mut new_samples: Vec<(PointSample, SecureField)> = Vec::new();
313                // If the column has two samples, we add a periodicity check. Note that this check
314                // is added even when the column is at its maximal size, in which case the
315                // periodicity sample coincides with the OOD point sample. This simplifies the
316                // Verifier logic by avoiding the need to track column sizes.
317                if let [_prev_point_sample, point_sample] = &samples_per_cols[..] {
318                    let period_generator = lifting_domain_generator.repeated_double(log_size);
319                    new_samples.push((
320                        PointSample {
321                            point: point_sample.point.add(period_generator.into_ef()),
322                            value: point_sample.value,
323                        },
324                        random_pows.next().unwrap(),
325                    ));
326                }
327                for sample in samples_per_cols.iter() {
328                    new_samples.push((sample.clone(), random_pows.next().unwrap()));
329                }
330                new_samples
331            })
332            .collect();
333        res.push(samples_with_randomness_and_periodicity);
334    }
335    TreeVec(res)
336}