From eb3011b02ad312522eb6d08c2d94561559ce1e36 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Tue, 4 May 2021 17:48:26 +0200 Subject: [PATCH] More work on polynomial commitments --- src/fri.rs | 188 ++++++++++++++++++++++++------- src/polynomial/commitment.rs | 138 +++++++++++++++++++++-- src/polynomial/old_polynomial.rs | 12 +- src/proof.rs | 9 +- 4 files changed, 290 insertions(+), 57 deletions(-) diff --git a/src/fri.rs b/src/fri.rs index ee85df05..550560a6 100644 --- a/src/fri.rs +++ b/src/fri.rs @@ -1,13 +1,13 @@ use crate::field::fft::fft; use crate::field::field::Field; -use crate::field::lagrange::{barycentric_weights, interpolate}; +use crate::field::lagrange::{barycentric_weights, interpolant, interpolate}; use crate::hash::hash_n_to_1; use crate::merkle_proofs::verify_merkle_proof; use crate::merkle_tree::MerkleTree; use crate::plonk_challenger::Challenger; use crate::plonk_common::reduce_with_powers; use crate::polynomial::polynomial::{PolynomialCoeffs, PolynomialValues}; -use crate::proof::{FriProof, FriQueryRound, FriQueryStep, Hash}; +use crate::proof::{FriInitialTreeProof, FriProof, FriQueryRound, FriQueryStep, Hash}; use crate::util::{log2_strict, reverse_bits, reverse_index_bits_in_place}; use anyhow::{ensure, Result}; @@ -56,32 +56,35 @@ fn fri_l(codeword_len: usize, rate_log: usize, conjecture: bool) -> f64 { /// Builds a FRI proof. pub fn fri_proof( - // Coefficients of the polynomial on which the LDT is performed. - // Only the first `1/rate` coefficients are non-zero. - polynomial_coeffs: &PolynomialCoeffs, + initial_merkle_trees: &[MerkleTree], + // Coefficients of the polynomial on which the LDT is performed. Only the first `1/rate` coefficients are non-zero. + lde_polynomial_coeffs: &PolynomialCoeffs, // Evaluation of the polynomial on the large domain. - polynomial_values: &PolynomialValues, + lde_polynomial_values: &PolynomialValues, challenger: &mut Challenger, config: &FriConfig, ) -> FriProof { - let n = polynomial_values.values.len(); - assert_eq!(polynomial_coeffs.coeffs.len(), n); + let n = lde_polynomial_values.values.len(); + assert_eq!(lde_polynomial_coeffs.coeffs.len(), n); // Commit phase - let (trees, final_coeffs) = - fri_committed_trees(polynomial_coeffs, polynomial_values, challenger, config); + let (trees, final_coeffs) = fri_committed_trees( + lde_polynomial_coeffs, + lde_polynomial_values, + challenger, + config, + ); // PoW phase let current_hash = challenger.get_hash(); let pow_witness = fri_proof_of_work(current_hash, config); // Query phase - let query_round_proofs = fri_prover_query_rounds(&trees, challenger, n, config); + let query_round_proofs = + fri_prover_query_rounds(initial_merkle_trees, &trees, challenger, n, config); FriProof { commit_phase_merkle_roots: trees.iter().map(|t| t.root).collect(), - // TODO: Fix this - initial_merkle_proofs: vec![], query_round_proofs, final_poly: final_coeffs, pow_witness, @@ -180,17 +183,19 @@ fn fri_verify_proof_of_work( } fn fri_prover_query_rounds( + initial_merkle_trees: &[MerkleTree], trees: &[MerkleTree], challenger: &mut Challenger, n: usize, config: &FriConfig, ) -> Vec> { (0..config.num_query_rounds) - .map(|_| fri_prover_query_round(trees, challenger, n, config)) + .map(|_| fri_prover_query_round(initial_merkle_trees, trees, challenger, n, config)) .collect() } fn fri_prover_query_round( + initial_merkle_trees: &[MerkleTree], trees: &[MerkleTree], challenger: &mut Challenger, n: usize, @@ -201,20 +206,17 @@ fn fri_prover_query_round( let x = challenger.get_challenge(); let mut domain_size = n; let mut x_index = x.to_canonical_u64() as usize % n; + let mut x_index = 0; + let initial_proof = initial_merkle_trees + .iter() + .map(|t| (t.get(x_index).to_vec(), t.prove(x_index))) + .collect::>(); for (i, tree) in trees.iter().enumerate() { let arity_bits = config.reduction_arity_bits[i]; let arity = 1 << arity_bits; - let next_domain_size = domain_size >> arity_bits; - let evals = if i == 0 { - // For the first layer, we need to send the evaluation at `x` too. - tree.get(x_index >> arity_bits).to_vec() - } else { - // For the other layers, we don't need to send the evaluation at `x`, since it can - // be inferred by the verifier. See the `compute_evaluation` function. - let mut evals = tree.get(x_index >> arity_bits).to_vec(); - evals.remove(x_index & (arity - 1)); - evals - }; + let mut evals = tree.get(x_index >> arity_bits).to_vec(); + dbg!(i, x_index, x_index & (arity - 1), &evals); + evals.remove(x_index & (arity - 1)); let merkle_proof = tree.prove(x_index >> arity_bits); query_steps.push(FriQueryStep { @@ -222,10 +224,15 @@ fn fri_prover_query_round( merkle_proof, }); - domain_size = next_domain_size; + domain_size >>= arity_bits; x_index >>= arity_bits; } - FriQueryRound { steps: query_steps } + FriQueryRound { + initial_trees_proof: FriInitialTreeProof { + evals_proofs: initial_proof, + }, + steps: query_steps, + } } /// Computes P'(x^arity) from {P(x*g^i)}_(i=0..arity), where g is a `arity`-th root of unity @@ -256,13 +263,25 @@ fn compute_evaluation( interpolate(&points, beta, &barycentric_weights) } -fn verify_fri_proof( +pub fn verify_fri_proof( purported_degree_log: usize, + // Point-evaluation pairs for polynomial commitments. + points: &[(F, F)], + // Scaling factor to combine polynomials. + alpha: F, + initial_merkle_roots: &[Hash], proof: &FriProof, challenger: &mut Challenger, config: &FriConfig, ) -> Result<()> { let total_arities = config.reduction_arity_bits.iter().sum::(); + dbg!( + purported_degree_log, + log2_strict(proof.final_poly.len()) + total_arities - config.rate_bits, + log2_strict(proof.final_poly.len()), + total_arities, + config.rate_bits, + ); ensure!( purported_degree_log == log2_strict(proof.final_poly.len()) + total_arities - config.rate_bits, @@ -296,14 +315,71 @@ fn verify_fri_proof( "Number of reductions should be non-zero." ); + dbg!(&points); + let interpolant = interpolant(points); for round_proof in &proof.query_round_proofs { - fri_verifier_query_round(&proof, challenger, n, &betas, round_proof, config)?; + fri_verifier_query_round( + &interpolant, + points, + alpha, + initial_merkle_roots, + &proof, + challenger, + n, + &betas, + round_proof, + config, + )?; } Ok(()) } +fn fri_verify_initial_proof( + x_index: usize, + proof: &FriInitialTreeProof, + initial_merkle_roots: &[Hash], +) -> Result<()> { + for ((evals, merkle_proof), &root) in proof.evals_proofs.iter().zip(initial_merkle_roots) { + verify_merkle_proof(evals.clone(), x_index, root, merkle_proof, false)?; + } + + Ok(()) +} + +fn fri_combine_initial( + proof: &FriInitialTreeProof, + alpha: F, + interpolant: &PolynomialCoeffs, + points: &[(F, F)], + subgroup_x: F, +) -> F { + dbg!(proof + .evals_proofs + .iter() + .map(|(v, _)| v) + .collect::>()); + let e = proof + .evals_proofs + .iter() + .map(|(v, _)| v) + .flatten() + .rev() + .fold(F::ZERO, |acc, &e| alpha * acc + e); + dbg!(e); + let numerator = e - interpolant.eval(subgroup_x); + dbg!(numerator); + dbg!(&points); + let denominator = points.iter().fold(F::ONE, |acc, &(x, _)| subgroup_x - x); + dbg!(denominator); + numerator / denominator +} + fn fri_verifier_query_round( + interpolant: &PolynomialCoeffs, + points: &[(F, F)], + alpha: F, + initial_merkle_roots: &[Hash], proof: &FriProof, challenger: &mut Challenger, n: usize, @@ -311,10 +387,16 @@ fn fri_verifier_query_round( round_proof: &FriQueryRound, config: &FriConfig, ) -> Result<()> { - let mut evaluations = Vec::new(); + let mut evaluations: Vec> = Vec::new(); let x = challenger.get_challenge(); let mut domain_size = n; let mut x_index = x.to_canonical_u64() as usize % n; + let mut x_index = 0; + fri_verify_initial_proof( + x_index, + &round_proof.initial_trees_proof, + initial_merkle_roots, + )?; let mut old_x_index = 0; // `subgroup_x` is `subgroup[x_index]`, i.e., the actual field element in the domain. let log_n = log2_strict(n); @@ -323,24 +405,30 @@ fn fri_verifier_query_round( for (i, &arity_bits) in config.reduction_arity_bits.iter().enumerate() { let arity = 1 << arity_bits; let next_domain_size = domain_size >> arity_bits; - if i == 0 { - let evals = round_proof.steps[0].evals.clone(); - evaluations.push(evals); + let e_x = if i == 0 { + fri_combine_initial( + &round_proof.initial_trees_proof, + alpha, + interpolant, + points, + subgroup_x, + ) } else { let last_evals = &evaluations[i - 1]; // Infer P(y) from {P(x)}_{x^arity=y}. - let e_x = compute_evaluation( + compute_evaluation( subgroup_x, old_x_index, config.reduction_arity_bits[i - 1], last_evals, betas[i - 1], - ); - let mut evals = round_proof.steps[i].evals.clone(); - // Insert P(y) into the evaluation vector, since it wasn't included by the prover. - evals.insert(x_index & (arity - 1), e_x); - evaluations.push(evals); + ) }; + let mut evals = round_proof.steps[i].evals.clone(); + // Insert P(y) into the evaluation vector, since it wasn't included by the prover. + evals.insert(x_index & (arity - 1), e_x); + evaluations.push(evals); + dbg!(i, &evaluations[i]); verify_merkle_proof( evaluations[i].clone(), x_index >> arity_bits, @@ -409,11 +497,29 @@ mod tests { proof_of_work_bits: 2, reduction_arity_bits, }; + let tree = { + let mut leaves = coset_lde + .values + .iter() + .map(|&x| vec![x]) + .collect::>(); + reverse_index_bits_in_place(&mut leaves); + MerkleTree::new(leaves, false) + }; + let root = tree.root; let mut challenger = Challenger::new(); - let proof = fri_proof(&coeffs, &coset_lde, &mut challenger, &config); + let proof = fri_proof(&[tree], &coeffs, &coset_lde, &mut challenger, &config); let mut challenger = Challenger::new(); - verify_fri_proof(degree_log, &proof, &mut challenger, &config)?; + verify_fri_proof( + degree_log, + &[], + F::ONE, + &[root], + &proof, + &mut challenger, + &config, + )?; Ok(()) } diff --git a/src/polynomial/commitment.rs b/src/polynomial/commitment.rs index 1f064b9e..601d3b30 100644 --- a/src/polynomial/commitment.rs +++ b/src/polynomial/commitment.rs @@ -1,13 +1,16 @@ use crate::field::fft::fft; use crate::field::field::Field; use crate::field::lagrange::{interpolant, interpolate}; -use crate::fri::{fri_proof, FriConfig}; +use crate::fri::{fri_proof, verify_fri_proof, FriConfig}; +use crate::merkle_proofs::verify_merkle_proof; use crate::merkle_tree::MerkleTree; use crate::plonk_challenger::Challenger; use crate::plonk_common::reduce_with_powers; use crate::polynomial::old_polynomial::Polynomial; use crate::polynomial::polynomial::{PolynomialCoeffs, PolynomialValues}; -use crate::util::transpose; +use crate::proof::{FriProof, Hash}; +use crate::util::{log2_strict, reverse_index_bits_in_place, transpose}; +use anyhow::Result; struct ListPolynomialCommitment { pub polynomials: Vec>, @@ -40,7 +43,10 @@ impl ListPolynomialCommitment { })) .collect::>(); - let merkle_tree = MerkleTree::new(transpose(&lde_values), false); + let mut leaves = transpose(&lde_values); + reverse_index_bits_in_place(&mut leaves); + // let merkle_tree = MerkleTree::new(transpose(&lde_values), false); + let merkle_tree = MerkleTree::new(leaves, false); Self { polynomials, @@ -73,14 +79,21 @@ impl ListPolynomialCommitment { challenger.observe_elements(evals); } + challenger.observe_hash(&self.merkle_tree.root); let alpha = challenger.get_challenge(); + dbg!(self + .polynomials + .iter() + .map(|p| p.eval(F::MULTIPLICATIVE_GROUP_GENERATOR)) + .collect::>()); let scaled_poly = self .polynomials .iter() .rev() .map(|p| p.clone().into()) .fold(Polynomial::empty(), |acc, p| acc.scalar_mul(alpha).add(&p)); + dbg!(scaled_poly.eval(F::MULTIPLICATIVE_GROUP_GENERATOR)); let scaled_evals = evaluations .iter() .map(|e| reduce_with_powers(e, alpha)) @@ -93,25 +106,128 @@ impl ListPolynomialCommitment { .collect::>(); debug_assert!(pairs.iter().all(|&(x, e)| scaled_poly.eval(x) == e)); + dbg!(&pairs); let interpolant: Polynomial = interpolant(&pairs).into(); - let denominator = points.iter().fold(Polynomial::empty(), |acc, &x| { - acc.mul(&vec![-x, F::ONE].into()) - }); + let denominator = points + .iter() + .fold(Polynomial::from(vec![F::ONE]), |acc, &x| { + acc.mul(&vec![-x, F::ONE].into()) + }); + dbg!(&denominator); let numerator = scaled_poly.add(&interpolant.neg()); - let (mut quotient, rem) = numerator.polynomial_division(&denominator); + for x in points { + dbg!(numerator.eval(*x)); + } + dbg!(numerator.eval(F::MULTIPLICATIVE_GROUP_GENERATOR)); + dbg!(denominator.eval(F::MULTIPLICATIVE_GROUP_GENERATOR)); + let (mut quotient, rem) = numerator.polynomial_long_division(&denominator); + dbg!(&numerator); + dbg!(quotient.mul(&denominator).add(&rem)); + dbg!("ient); + dbg!(&rem); debug_assert!(rem.is_zero()); + quotient.pad(quotient.degree().next_power_of_two()); - let quotient_values = fft(quotient.clone().into()); + let lde_quotient = PolynomialCoeffs::from(quotient.clone()).lde(self.fri_config.rate_bits); + let lde_quotient_values = fft(lde_quotient.clone()); + let fri_proof = fri_proof( - "ient.into(), - "ient_values, + &[self.merkle_tree.clone()], + &lde_quotient, + &lde_quotient_values, challenger, &self.fri_config, ); - todo!() + + OpeningProof { + evaluations, + merkle_root: self.merkle_tree.root, + fri_proof, + quotient_degree: quotient.len(), + } } } pub struct OpeningProof { evaluations: Vec>, + merkle_root: Hash, + fri_proof: FriProof, + quotient_degree: usize, +} + +impl OpeningProof { + pub fn verify( + &self, + points: &[F], + challenger: &mut Challenger, + fri_config: &FriConfig, + ) -> Result<()> { + for evals in &self.evaluations { + challenger.observe_elements(evals); + } + + challenger.observe_hash(&self.merkle_root); + let alpha = challenger.get_challenge(); + + let scaled_evals = self + .evaluations + .iter() + .map(|e| reduce_with_powers(e, alpha)) + .collect::>(); + + let pairs = points + .iter() + .zip(&scaled_evals) + .map(|(&x, &e)| (x, e)) + .collect::>(); + + dbg!(self.quotient_degree); + verify_fri_proof( + log2_strict(self.quotient_degree), + &pairs, + alpha, + &[self.merkle_root], + &self.fri_proof, + challenger, + fri_config, + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::field::crandall_field::CrandallField; + use anyhow::Result; + + #[test] + fn test_polynomial_commitment() -> Result<()> { + type F = CrandallField; + + let k = 1; + let degree_log = 3; + let degree = 1 << degree_log; + + let fri_config = FriConfig { + proof_of_work_bits: 2, + rate_bits: 2, + reduction_arity_bits: vec![3, 2, 1], + num_query_rounds: 1, + }; + + let polys = (0..k) + // .map(|_| PolynomialCoeffs::new((0..degree).map(|_| F::rand()).collect())) + .map(|_| PolynomialCoeffs::new((0..degree).map(|i| F::from_canonical_u64(i)).collect())) + .collect(); + + let lpc = ListPolynomialCommitment::new(polys, &fri_config, false); + + let num_points = 3; + let points = (0..num_points).map(|_| F::rand()).collect::>(); + let points = vec![-F::TWO, -F::ONE - F::TWO, -F::TWO - F::TWO]; + + let proof = lpc.open(&points, &mut Challenger::new()); + + proof.verify(&points, &mut Challenger::new(), &fri_config) + } } diff --git a/src/polynomial/old_polynomial.rs b/src/polynomial/old_polynomial.rs index cbf7b4d8..31002b01 100644 --- a/src/polynomial/old_polynomial.rs +++ b/src/polynomial/old_polynomial.rs @@ -235,10 +235,11 @@ impl Polynomial { } let a_deg = self.degree(); let b_deg = b.degree(); - let a_pad = self.padded(a_deg + b_deg + 1); - let b_pad = b.padded(a_deg + b_deg + 1); + let new_deg = (a_deg + b_deg + 1).next_power_of_two(); + let a_pad = self.padded(new_deg); + let b_pad = b.padded(new_deg); - let precomputation = fft_precompute(a_deg + b_deg + 1); + let precomputation = fft_precompute(new_deg); let a_evals = fft_with_precomputation_power_of_2(a_pad.0.to_vec().into(), &precomputation); let b_evals = fft_with_precomputation_power_of_2(b_pad.0.to_vec().into(), &precomputation); @@ -275,11 +276,16 @@ impl Polynomial { let cur_q_degree = remainder.degree() - b_degree; quotient[cur_q_degree] = cur_q_coeff; + dbg!(cur_q_coeff, cur_q_degree); + dbg!(&b); for (i, &div_coeff) in b.iter().enumerate() { + dbg!(i, div_coeff, remainder[cur_q_degree + i]); remainder[cur_q_degree + i] = remainder[cur_q_degree + i] - (cur_q_coeff * div_coeff); + dbg!(remainder[cur_q_degree + i]); } remainder.trim(); + dbg!(&remainder); } (quotient, remainder) } diff --git a/src/proof.rs b/src/proof.rs index f37081c0..cfdaaccb 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -90,17 +90,22 @@ pub struct FriQueryStep { pub merkle_proof: MerkleProof, } +/// Evaluations and Merkle proof produced by the prover in a FRI query step. +// TODO: Implement FriInitialTreeProofTarget +pub struct FriInitialTreeProof { + pub evals_proofs: Vec<(Vec, MerkleProof)>, +} + /// Proof for a FRI query round. // TODO: Implement FriQueryRoundTarget pub struct FriQueryRound { + pub initial_trees_proof: FriInitialTreeProof, pub steps: Vec>, } pub struct FriProof { /// A Merkle root for each reduced polynomial in the commit phase. pub commit_phase_merkle_roots: Vec>, - /// Merkle proofs for the original purported codewords, i.e. the subject of the LDT. - pub initial_merkle_proofs: Vec>, /// Query rounds proofs pub query_round_proofs: Vec>, /// The final polynomial in coefficient form.