Add FRI PCS minimial implementation
arnaucube root@arnaucube.com
Sat, 25 Mar 2023 08:21:39 +0100
1 files changed,
174 insertions(+),
34 deletions(-)
jump to
M
src/lib.rs
→
src/lib.rs
@@ -14,11 +14,22 @@ };
use ark_std::cfg_into_iter; use ark_std::marker::PhantomData; +use ark_std::ops::Div; use ark_std::ops::Mul; // rho^-1 -const rho1: usize = 8; // WIP +const rho1: usize = 8; // WIP TODO parametrize + +// FRI low degree testing proof +pub struct LDTProof<F: PrimeField> { + degree: usize, // claimed degree + commitments: Vec<F>, + mtproofs: Vec<Vec<F>>, + evals: Vec<F>, + constants: [F; 2], +} +// FRI_LDT implements the FRI Low Degree Testing pub struct FRI_LDT<F: PrimeField, P: DenseUVPolynomial<F>, H: Hash<F>> { _f: PhantomData<F>, _poly: PhantomData<P>,@@ -35,13 +46,14 @@ }
} fn split(p: &P) -> (P, P) { + // TODO see if enable check, take in mind g(x) being d-1 + // let d = p.coeffs().len(); + // if (d != 0) && (d & (d - 1) != 0) { + // println!("d={:?}", d); + // panic!("d should be a power of 2"); + // } + // let d = p.degree() + 1; - let d = p.coeffs().len(); - if (d != 0) && (d & (d - 1) != 0) { - println!("d={:?}", d); - panic!("d should be a power of 2"); - } - let coeffs = p.coeffs(); let odd: Vec<F> = coeffs.iter().step_by(2).cloned().collect(); let even: Vec<F> = coeffs.iter().skip(1).step_by(2).cloned().collect();@@ -53,7 +65,8 @@ );
} // prove implements the proof generation for a FRI-low-degree-testing - pub fn prove(p: &P) -> (Vec<F>, Vec<Vec<F>>, Vec<F>, [F; 2]) { + // pub fn prove(p: &P) -> (Vec<F>, Vec<Vec<F>>, Vec<F>, [F; 2]) { + pub fn prove(p: &P) -> LDTProof<F> { // init transcript let mut transcript: Transcript<F> = Transcript::<F>::new();@@ -128,39 +141,48 @@ }
let constant_fL_l: F = fL_i.coeffs()[0].clone(); let constant_fR_l: F = fR_i.coeffs()[0].clone(); - (commitments, mtproofs, evals, [constant_fL_l, constant_fR_l]) + LDTProof { + degree: p.degree(), + commitments, + mtproofs, + evals, + constants: [constant_fL_l, constant_fR_l], + } } // verify implements the verification of a FRI-low-degree-testing proof pub fn verify( + proof: LDTProof<F>, degree: usize, // expected degree - commitments: Vec<F>, - mtproofs: Vec<Vec<F>>, - evals: Vec<F>, - constants: [F; 2], ) -> bool { // init transcript let mut transcript: Transcript<F> = Transcript::<F>::new(); - let sub_order = rho1 * degree; // TMP, TODO this will depend on rho parameter + if degree != proof.degree { + println!("proof degree missmatch"); + return false; + } + // TODO check that log_2(evals/2) == degree, etc + + let sub_order = rho1 * degree; let eval_sub_domain: GeneralEvaluationDomain<F> = GeneralEvaluationDomain::new(sub_order).unwrap(); let (z_pos, z) = transcript.get_challenge_in_eval_domain(eval_sub_domain, b"get z"); - if commitments.len() != (evals.len() / 2) { + if proof.commitments.len() != (proof.evals.len() / 2) { println!("sho commitments.len() != (evals.len() / 2) - 1"); return false; } let mut i_z = 0; - for i in (0..evals.len()).step_by(2) { + for i in (0..proof.evals.len()).step_by(2) { let alpha_i = transcript.get_challenge(b"get alpha_i"); // take f_i(z^2) from evals let z_2i = z.pow([2_u64.pow(i_z as u32)]); // z^{2^i} - let fi_z = evals[i]; - let neg_fi_z = evals[i + 1]; + let fi_z = proof.evals[i]; + let neg_fi_z = proof.evals[i + 1]; // compute f_i^L(z^2), f_i^R(z^2) from the linear combination let L = (fi_z + neg_fi_z) * F::from(2_u32).inverse().unwrap(); let R = (fi_z - neg_fi_z) * (F::from(2_u32) * z_2i).inverse().unwrap();@@ -169,8 +191,8 @@ // compute f_{i+1}(z^2) = f_i^L(z^2) + a_i f_i^R(z^2)
let next_fi_z2 = L + alpha_i * R; // check: obtained f_{i+1}(z^2) == evals.f_{i+1}(z^2) (=evals[i+2]) - if i < evals.len() - 2 { - if next_fi_z2 != evals[i + 2] { + if i < proof.evals.len() - 2 { + if next_fi_z2 != proof.evals[i + 2] { println!( "verify step i={}, should f_i+1(z^2) == evals.f_i+1(z^2) (=evals[i+2])", i@@ -178,16 +200,16 @@ );
return false; } } - transcript.add(b"root_i", &commitments[i_z]); - transcript.add(b"f_i(z^{2^i})", &evals[i]); - transcript.add(b"f_i(-z^{2^i})", &evals[i + 1]); + transcript.add(b"root_i", &proof.commitments[i_z]); + transcript.add(b"f_i(z^{2^i})", &proof.evals[i]); + transcript.add(b"f_i(-z^{2^i})", &proof.evals[i + 1]); // check commitment opening if !MerkleTree::<F, H>::verify( - commitments[i_z], + proof.commitments[i_z], F::from(z_pos as u32), - evals[i], - mtproofs[i_z].clone(), + proof.evals[i], + proof.mtproofs[i_z].clone(), ) { println!("verify step i={}, MT::verify failed", i); return false;@@ -195,12 +217,12 @@ }
// last iteration, check constant values equal to the obtained f_i^L(z^{2^i}), // f_i^R(z^{2^i}) - if i == evals.len() - 2 { - if L != constants[0] { + if i == proof.evals.len() - 2 { + if L != proof.constants[0] { println!("constant L not equal to the obtained one"); return false; } - if R != constants[1] { + if R != proof.constants[1] { println!("constant R not equal to the obtained one"); return false; }@@ -212,6 +234,103 @@ true
} } +pub struct FRI_PCS_Proof<F: PrimeField> { + p_proof: LDTProof<F>, + g_proof: LDTProof<F>, + mtproof_y: Vec<F>, + claimed_y: F, +} + +// FRI_PCS implements the FRI Polynomial Commitment +pub struct FRI_PCS<F: PrimeField, P: DenseUVPolynomial<F>, H: Hash<F>> { + _F: PhantomData<F>, + _poly: PhantomData<P>, + _h: PhantomData<H>, +} + +impl<F: PrimeField, P: DenseUVPolynomial<F>, H: Hash<F>> FRI_PCS<F, P, H> +where + for<'a, 'b> &'a P: Div<&'b P, Output = P>, +{ + pub fn commit(p: &P) -> (F, MerkleTree<F, H>) { + let d = p.degree(); + let sub_order = d * rho1; + let eval_sub_domain: GeneralEvaluationDomain<F> = + GeneralEvaluationDomain::new(sub_order).unwrap(); + let subdomain_evaluations: Vec<F> = cfg_into_iter!(0..eval_sub_domain.size()) + .map(|k| p.evaluate(&eval_sub_domain.element(k))) + .collect(); + MerkleTree::<F, H>::commit(&subdomain_evaluations) + } + + pub fn open(p: &P, commitment_mt: MerkleTree<F, H>, r: F) -> FRI_PCS_Proof<F> { + let y = p.evaluate(&r); + let y_poly: P = P::from_coefficients_vec(vec![y]); + let mut p_y: P = p.clone(); + p_y.sub_assign(&y_poly); + // p_y = p_y - y_poly; + let x_r: P = P::from_coefficients_vec(vec![-r, F::one()]); + + // g(x), quotient polynomial + let g: P = p_y.div(&x_r); + + if p.degree() != g.degree() + 1 { + panic!("ERR p.deg: {}, g.deg: {}", p.degree(), g.degree()); // TODO err + } + + // TODO proof for commitment + let y_eval_index = F::from(3_u32); // TODO find y in subdomain_evaluations + let mtproof_y = commitment_mt.open(y_eval_index); + + let p_proof = FRI_LDT::<F, P, H>::prove(p); + let g_proof = FRI_LDT::<F, P, H>::prove(&g); + + FRI_PCS_Proof { + p_proof, + g_proof, + mtproof_y, + claimed_y: y, + } + } + + pub fn verify(commitment: F, proof: FRI_PCS_Proof<F>, r: F, y: F) -> bool { + let deg_p = proof.p_proof.degree; + let deg_g = proof.g_proof.degree; + if deg_p != deg_g + 1 { + return false; + } + + // obtain z from transcript + let sub_order = rho1 * proof.p_proof.degree; + let eval_sub_domain: GeneralEvaluationDomain<F> = + GeneralEvaluationDomain::new(sub_order).unwrap(); + let mut transcript: Transcript<F> = Transcript::<F>::new(); + let (_, z) = transcript.get_challenge_in_eval_domain(eval_sub_domain, b"get z"); + + // check g(z) == (f(z) - y) * (z-r)^-1 + let gz = proof.g_proof.evals[0]; + let fz = proof.p_proof.evals[0]; + let rhs = (fz - y) / (z - r); + if gz != rhs { + return false; + } + + // TODO check commitment + + // check FRI-LDT for p(x) + if !FRI_LDT::<F, P, H>::verify(proof.p_proof, deg_p) { + return false; + } + + // check FRI-LDT for g(x) + if !FRI_LDT::<F, P, H>::verify(proof.g_proof, deg_p - 1) { + return false; + } + + return true; + } +} + #[cfg(test)] mod tests { use super::*;@@ -251,14 +370,35 @@ let p = DensePolynomial::<Fr>::rand(deg, &mut rng);
assert_eq!(p.degree(), deg); // println!("p {:?}", p); - type FRID = FRI_LDT<Fr, DensePolynomial<Fr>, Keccak256Hash<Fr>>; + type LDT = FRI_LDT<Fr, DensePolynomial<Fr>, Keccak256Hash<Fr>>; - let (commitments, mtproofs, evals, constvals) = FRID::prove(&p); + let proof = LDT::prove(&p); // commitments contains the commitments to each f_0, f_1, ..., f_n, with n=log2(d) - assert_eq!(commitments.len(), log2(p.coeffs().len()) as usize); - assert_eq!(evals.len(), 2 * log2(p.coeffs().len()) as usize); + assert_eq!(proof.commitments.len(), log2(p.coeffs().len()) as usize); + assert_eq!(proof.evals.len(), 2 * log2(p.coeffs().len()) as usize); + + let v = LDT::verify(proof, deg); + assert!(v); + } + + #[test] + fn test_polynomial_commitment() { + let mut rng = ark_std::test_rng(); - let v = FRID::verify(deg, commitments, mtproofs, evals, constvals); + let deg = 31; + let p = DensePolynomial::<Fr>::rand(deg, &mut rng); + + type PCS = FRI_PCS<Fr, DensePolynomial<Fr>, Keccak256Hash<Fr>>; + + let (commitment, commitment_mt) = PCS::commit(&p); + + // Verifier + let r = Fr::rand(&mut rng); + + let proof = PCS::open(&p, commitment_mt, r); + + let claimed_y = proof.claimed_y.clone(); // WIP + let v = PCS::verify(commitment, proof, r, claimed_y); assert!(v); } }