prover/src/composer/mod.rs (141 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. use super::{constraints::CompositionPoly, StarkDomain, TracePolyTable}; use air::{Air, DeepCompositionCoefficients, EvaluationFrame}; use core::marker::PhantomData; use math::{add_in_place, fft, log2, mul_acc, polynom, FieldElement, StarkField}; use utils::{collections::Vec, iter_mut}; #[cfg(feature = "concurrent")] use utils::iterators::*; // DEEP COMPOSITION POLYNOMIAL // ================================================================================================ pub struct DeepCompositionPoly<A: Air, E: FieldElement<BaseField = A::BaseField>> { coefficients: Vec<E>, cc: DeepCompositionCoefficients<E>, z: E, field_extension: bool, _air: PhantomData<A>, } impl<A: Air, E: FieldElement<BaseField = A::BaseField>> DeepCompositionPoly<A, E> { // CONSTRUCTOR // -------------------------------------------------------------------------------------------- /// Returns a new DEEP composition polynomial. Initially, this polynomial will be empty, and /// the intent is to populate the coefficients via add_trace_polys() and add_constraint_polys() /// methods. pub fn new(air: &A, z: E, cc: DeepCompositionCoefficients<E>) -> Self { DeepCompositionPoly { coefficients: vec![], cc, z, field_extension: !air.options().field_extension().is_none(), _air: PhantomData, } } // ACCESSORS // -------------------------------------------------------------------------------------------- /// Returns the size of the DEEP composition polynomial. pub fn poly_size(&self) -> usize { self.coefficients.len() } /// Returns the degree of the composition polynomial. pub fn degree(&self) -> usize { polynom::degree_of(&self.coefficients) } // TRACE POLYNOMIAL COMPOSITION // -------------------------------------------------------------------------------------------- /// Combines all trace polynomials into a single polynomial and saves the result into /// the DEEP composition polynomial. The combination is done as follows: /// /// - Compute polynomials T'_i(x) = (T_i(x) - T_i(z)) / (x - z) and /// T''_i(x) = (T_i(x) - T_i(z * g)) / (x - z * g) for all i, where T_i(x) is a trace /// polynomial for register i. /// - Then, combine together all T'_i(x) polynomials using random liner combination as /// T(x) = sum(T'_i(x) * cc'_i + T''_i(x) * cc''_i) for all i, where cc'_i and cc''_i are /// the coefficients for the random linear combination drawn from the public coin. /// - In cases when we generate the proof using an extension field, we also compute /// T'''_i(x) = (T_i(x) - T_i(z_conjugate)) / (x - z_conjugate), and add it to T(x) similarly /// to the way described above. This is needed in order to verify that the trace is defined /// over the base field, rather than the extension field. /// /// Note that evaluations of T_i(z) and T_i(z * g) are passed in via the `ood_frame` parameter. pub fn add_trace_polys( &mut self, trace_polys: TracePolyTable<A::BaseField>, ood_frame: EvaluationFrame<E>, ) { assert!(self.coefficients.is_empty()); // compute a second out-of-domain point offset from z by exactly trace generator; this // point defines the "next" computation state in relation to point z let trace_length = trace_polys.poly_size(); let g = E::from(A::BaseField::get_root_of_unity(log2(trace_length))); let next_z = self.z * g; // cache state of registers at points z and z * g let trace_state1 = ood_frame.current(); let trace_state2 = ood_frame.next(); // combine trace polynomials into 2 composition polynomials T'(x) and T''(x), and if // we are using a field extension, also T'''(x) let mut t1_composition = E::zeroed_vector(trace_length); let mut t2_composition = E::zeroed_vector(trace_length); let mut t3_composition = if self.field_extension { E::zeroed_vector(trace_length) } else { Vec::new() }; for (i, poly) in trace_polys.iter().enumerate() { // compute T'(x) = T(x) - T(z), multiply it by a pseudo-random coefficient, // and add the result into composition polynomial acc_poly( &mut t1_composition, poly, trace_state1[i], self.cc.trace[i].0, ); // compute T''(x) = T(x) - T(z * g), multiply it by a pseudo-random coefficient, // and add the result into composition polynomial acc_poly( &mut t2_composition, poly, trace_state2[i], self.cc.trace[i].1, ); // when extension field is enabled, compute T'''(x) = T(x) - T(z_conjugate), multiply // it by a pseudo-random coefficient, and add the result into composition polynomial if self.field_extension { acc_poly( &mut t3_composition, poly, trace_state1[i].conjugate(), self.cc.trace[i].2, ); } } // divide the composition polynomials by (x - z), (x - z * g), and (x - z_conjugate) // respectively, and add the resulting polynomials together; the output of this step // is a single trace polynomial T(x) and deg(T(x)) = trace_length - 2. let trace_poly = merge_trace_compositions( vec![t1_composition, t2_composition, t3_composition], vec![self.z, next_z, self.z.conjugate()], ); // set the coefficients of the DEEP composition polynomial self.coefficients = trace_poly; assert_eq!(self.poly_size() - 2, self.degree()); } // CONSTRAINT POLYNOMIAL COMPOSITION // -------------------------------------------------------------------------------------------- /// Divides out OOD point z from the constraint composition polynomial and saves the result /// into the DEEP composition polynomial. This method is intended to be called only after the /// add_trace_polys() method has been executed. The composition is done as follows: /// /// - For each H_i(x), compute H'_i(x) = (H_i(x) - H(z^m)) / (x - z^m), where H_i(x) is the /// ith composition polynomial column and m is the total number of columns. /// - Then, combine all H_i(x) polynomials together by computing H(x) = sum(H_i(x) * cc_i) for /// all i, where cc_i is the coefficient for the random linear combination drawn from the /// public coin. /// /// Note that evaluations of H_i(x) at z^m are passed in via the `ood_evaluations` parameter. pub fn add_composition_poly( &mut self, composition_poly: CompositionPoly<E>, ood_evaluations: Vec<E>, ) { assert!(!self.coefficients.is_empty()); // compute z^m let num_columns = composition_poly.num_columns() as u32; let z_m = self.z.exp(num_columns.into()); let mut column_polys = composition_poly.into_columns(); // Divide out the OOD point z from column polynomials iter_mut!(column_polys) .zip(ood_evaluations) .for_each(|(poly, value_at_z_m)| { // compute H'_i(x) = (H_i(x) - H_i(z^m)) / (x - z^m) poly[0] -= value_at_z_m; polynom::syn_div_in_place(poly, 1, z_m); }); // add H'_i(x) * cc_i for all i into the DEEP composition polynomial for (i, poly) in column_polys.into_iter().enumerate() { mul_acc(&mut self.coefficients, &poly, self.cc.constraints[i]); } assert_eq!(self.poly_size() - 2, self.degree()); } // FINAL DEGREE ADJUSTMENT // -------------------------------------------------------------------------------------------- /// Increase the degree of the DEEP composition polynomial by one. After add_trace_polys() and /// add_composition_poly() are executed, the degree of the DEEP composition polynomial is /// trace_length - 2 because in these functions we divide the polynomials of degree /// trace_length - 1 by (x - z), (x - z * g) etc. which decreases the degree by one. We want to /// ensure that degree of the DEEP composition polynomial is trace_length - 1, so we make the /// adjustment here by computing C'(x) = C(x) * (cc_0 + x * cc_1), where cc_0 and cc_1 are the /// coefficients for the random linear combination drawn from the public coin. pub fn adjust_degree(&mut self) { assert_eq!(self.poly_size() - 2, self.degree()); let mut result = E::zeroed_vector(self.coefficients.len()); // this is equivalent to C(x) * cc_0 mul_acc(&mut result, &self.coefficients, self.cc.degree.0); // this is equivalent to C(x) * x * cc_1 mul_acc( &mut result[1..], &self.coefficients[..(self.coefficients.len() - 1)], self.cc.degree.1, ); self.coefficients = result; assert_eq!(self.poly_size() - 1, self.degree()); } // LOW-DEGREE EXTENSION // -------------------------------------------------------------------------------------------- /// Evaluates DEEP composition polynomial over the specified LDE domain and returns the result. pub fn evaluate(self, domain: &StarkDomain<A::BaseField>) -> Vec<E> { fft::evaluate_poly_with_offset( &self.coefficients, domain.trace_twiddles(), domain.offset(), domain.trace_to_lde_blowup(), ) } } // HELPER FUNCTIONS // ================================================================================================ /// Divides each polynomial in the list by the corresponding divisor, and computes the /// coefficient-wise sum of all resulting polynomials. fn merge_trace_compositions<E: FieldElement>(mut polys: Vec<Vec<E>>, divisors: Vec<E>) -> Vec<E> { // divide all polynomials by their corresponding divisor iter_mut!(polys).zip(divisors).for_each(|(poly, divisor)| { // skip empty polynomials; this could happen for conjugate composition polynomial (T3) // when extension field is not enabled. if !poly.is_empty() { polynom::syn_div_in_place(poly, 1, divisor); } }); // add all polynomials together into a single polynomial let mut result = polys.remove(0); for poly in polys.iter() { if !poly.is_empty() { add_in_place(&mut result, poly); } } result } /// Computes (P(x) - value) * k and saves the result into the accumulator fn acc_poly<B, E>(accumulator: &mut [E], poly: &[B], value: E, k: E) where B: StarkField, E: FieldElement<BaseField = B>, { mul_acc(accumulator, poly, k); let adjusted_tz = value * k; accumulator[0] -= adjusted_tz; }