use anyhow::{ensure, Result}; use itertools::Itertools; use plonky2::field::extension_field::Extendable; use plonky2::field::field_types::Field; use plonky2::field::polynomial::{PolynomialCoeffs, PolynomialValues}; use plonky2::field::zero_poly_coset::ZeroPolyOnCoset; use plonky2::fri::oracle::PolynomialBatch; use plonky2::hash::hash_types::RichField; use plonky2::iop::challenger::Challenger; use plonky2::plonk::config::GenericConfig; use plonky2::timed; use plonky2::util::timing::TimingTree; use plonky2::util::transpose; use plonky2_util::{log2_ceil, log2_strict}; use rayon::prelude::*; use crate::config::StarkConfig; use crate::constraint_consumer::ConstraintConsumer; use crate::proof::{StarkOpeningSet, StarkProof, StarkProofWithPublicInputs}; use crate::stark::Stark; use crate::vars::StarkEvaluationVars; pub fn prove( stark: S, config: &StarkConfig, trace: Vec<[F; S::COLUMNS]>, public_inputs: [F; S::PUBLIC_INPUTS], timing: &mut TimingTree, ) -> Result> where F: RichField + Extendable, C: GenericConfig, S: Stark, [(); S::COLUMNS]:, [(); S::PUBLIC_INPUTS]:, { let degree = trace.len(); let degree_bits = log2_strict(degree); let trace_vecs = trace.into_iter().map(|row| row.to_vec()).collect_vec(); let trace_col_major: Vec> = transpose(&trace_vecs); let trace_poly_values: Vec> = timed!( timing, "compute trace polynomials", trace_col_major .par_iter() .map(|column| PolynomialValues::new(column.clone())) .collect() ); let rate_bits = config.fri_config.rate_bits; let cap_height = config.fri_config.cap_height; let trace_commitment = timed!( timing, "compute trace commitment", PolynomialBatch::::from_values( trace_poly_values, rate_bits, false, cap_height, timing, None, ) ); let trace_cap = trace_commitment.merkle_tree.cap.clone(); let mut challenger = Challenger::new(); challenger.observe_cap(&trace_cap); let alphas = challenger.get_n_challenges(config.num_challenges); let quotient_polys = compute_quotient_polys::( &stark, &trace_commitment, public_inputs, alphas, degree_bits, rate_bits, ); let all_quotient_chunks = quotient_polys .into_par_iter() .flat_map(|mut quotient_poly| { quotient_poly .trim_to_len(degree * stark.quotient_degree_factor()) .expect("Quotient has failed, the vanishing polynomial is not divisible by Z_H"); // Split quotient into degree-n chunks. quotient_poly.chunks(degree) }) .collect(); let quotient_commitment = timed!( timing, "compute quotient commitment", PolynomialBatch::from_coeffs( all_quotient_chunks, rate_bits, false, config.fri_config.cap_height, timing, None, ) ); let quotient_polys_cap = quotient_commitment.merkle_tree.cap.clone(); challenger.observe_cap("ient_polys_cap); let zeta = challenger.get_extension_challenge::(); // To avoid leaking witness data, we want to ensure that our opening locations, `zeta` and // `g * zeta`, are not in our subgroup `H`. It suffices to check `zeta` only, since // `(g * zeta)^n = zeta^n`, where `n` is the order of `g`. let g = F::Extension::primitive_root_of_unity(degree_bits); ensure!( zeta.exp_power_of_2(degree_bits) != F::Extension::ONE, "Opening point is in the subgroup." ); let openings = StarkOpeningSet::new(zeta, g, &trace_commitment, "ient_commitment); challenger.observe_openings(&openings.to_fri_openings()); // TODO: Add permuation checks let initial_merkle_trees = &[&trace_commitment, "ient_commitment]; let fri_params = config.fri_params(degree_bits); let opening_proof = timed!( timing, "compute openings proof", PolynomialBatch::prove_openings( &stark.fri_instance(zeta, g, rate_bits, config.num_challenges), initial_merkle_trees, &mut challenger, &fri_params, timing, ) ); let proof = StarkProof { trace_cap, quotient_polys_cap, openings, opening_proof, }; Ok(StarkProofWithPublicInputs { proof, public_inputs: public_inputs.to_vec(), }) } /// Computes the quotient polynomials `(sum alpha^i C_i(x)) / Z_H(x)` for `alpha` in `alphas`, /// where the `C_i`s are the Stark constraints. fn compute_quotient_polys( stark: &S, trace_commitment: &PolynomialBatch, public_inputs: [F; S::PUBLIC_INPUTS], alphas: Vec, degree_bits: usize, rate_bits: usize, ) -> Vec> where F: RichField + Extendable, C: GenericConfig, S: Stark, [(); S::COLUMNS]:, [(); S::PUBLIC_INPUTS]:, { let degree = 1 << degree_bits; let quotient_degree_bits = log2_ceil(stark.quotient_degree_factor()); assert!( quotient_degree_bits <= rate_bits, "Having constraints of degree higher than the rate is not supported yet." ); let step = 1 << (rate_bits - quotient_degree_bits); // When opening the `Z`s polys at the "next" point, need to look at the point `next_step` steps away. let next_step = 1 << quotient_degree_bits; // Evaluation of the first Lagrange polynomial on the LDE domain. let lagrange_first = PolynomialValues::selector(degree, 0).lde_onto_coset(quotient_degree_bits); // Evaluation of the last Lagrange polynomial on the LDE domain. let lagrange_last = PolynomialValues::selector(degree, degree - 1).lde_onto_coset(quotient_degree_bits); let z_h_on_coset = ZeroPolyOnCoset::::new(degree_bits, quotient_degree_bits); // Retrieve the LDE values at index `i`. let get_at_index = |comm: &PolynomialBatch, i: usize| -> [F; S::COLUMNS] { comm.get_lde_values(i * step).try_into().unwrap() }; // Last element of the subgroup. let last = F::primitive_root_of_unity(degree_bits).inverse(); let size = degree << quotient_degree_bits; let coset = F::cyclic_subgroup_coset_known_order( F::primitive_root_of_unity(degree_bits + quotient_degree_bits), F::coset_shift(), size, ); let quotient_values = (0..size) .into_par_iter() .map(|i| { // TODO: Set `P` to a genuine `PackedField` here. let mut consumer = ConstraintConsumer::::new( alphas.clone(), coset[i] - last, lagrange_first.values[i], lagrange_last.values[i], ); let vars = StarkEvaluationVars:: { local_values: &get_at_index(trace_commitment, i), next_values: &get_at_index(trace_commitment, (i + next_step) % size), public_inputs: &public_inputs, }; stark.eval_packed_base(vars, &mut consumer); // TODO: Fix this once we use a genuine `PackedField`. let mut constraints_evals = consumer.accumulators(); // We divide the constraints evaluations by `Z_H(x)`. let denominator_inv = z_h_on_coset.eval_inverse(i); for eval in &mut constraints_evals { *eval *= denominator_inv; } constraints_evals }) .collect::>(); transpose("ient_values) .into_par_iter() .map(PolynomialValues::new) .map(|values| values.coset_ifft(F::coset_shift())) .collect() }