FRI-low-degree-testing working
arnaucube root@arnaucube.com
Mon, 06 Mar 2023 21:11:38 +0100
3 files changed,
132 insertions(+),
53 deletions(-)
M
src/lib.rs
→
src/lib.rs
@@ -1,21 +1,25 @@
#![allow(non_snake_case)] +#![allow(non_camel_case_types)] pub mod merkletree; use merkletree::{MerkleTreePoseidon as MT, Params as MTParams}; use ark_ff::PrimeField; -use ark_poly::{univariate::DensePolynomial, UVPolynomial}; +use ark_poly::{ + univariate::DensePolynomial, EvaluationDomain, GeneralEvaluationDomain, UVPolynomial, +}; + use ark_std::log2; use ark_std::marker::PhantomData; use ark_std::ops::Mul; -use ark_std::{rand::Rng, UniformRand}; +use ark_std::{cfg_into_iter, rand::Rng, UniformRand}; -pub struct FRI<F: PrimeField, P: UVPolynomial<F>> { +pub struct FRI_LDT<F: PrimeField, P: UVPolynomial<F>> { _f: PhantomData<F>, _poly: PhantomData<P>, } -impl<F: PrimeField, P: UVPolynomial<F>> FRI<F, P> { +impl<F: PrimeField, P: UVPolynomial<F>> FRI_LDT<F, P> { pub fn new() -> Self { Self { _f: PhantomData,@@ -41,17 +45,41 @@ P::from_coefficients_vec(even),
); } - pub fn prove<R: Rng>(rng: &mut R, p: &P) -> (Vec<F>, Vec<F>, [F; 2]) { + // prove implements the proof generation for a FRI-low-degree-testing + pub fn prove<R: Rng>(rng: &mut R, p: &P) -> (Vec<F>, Vec<Vec<F>>, Vec<F>, [F; 2]) { + let d = p.degree(); + let mut commitments: Vec<F> = Vec::new(); + let mut mts: Vec<MT<F>> = Vec::new(); + // f_0(x) = fL_0(x^2) + x fR_0(x^2) let mut f_i1 = p.clone(); - // TODO challenge a_0 + // sub_order = |F_i| = rho^-1 * d + let mut sub_order = d; // TMP, TODO this will depend on rho parameter + let mut eval_sub_domain: GeneralEvaluationDomain<F> = + GeneralEvaluationDomain::new(sub_order).unwrap(); + + // TODO merge in the next for loop + let evals: Vec<F> = cfg_into_iter!(0..eval_sub_domain.size()) + .map(|k| f_i1.evaluate(&eval_sub_domain.element(k))) + .collect(); + let (cm_i, mt_i) = MT::commit(&evals); + commitments.push(cm_i); + mts.push(mt_i); + sub_order = sub_order / 2; + eval_sub_domain = GeneralEvaluationDomain::new(sub_order).unwrap(); + // + + // V sets rand z \in \mathbb{F} challenge + // TODO this will be a hash from the transcript + let z_pos = 3; + let z = eval_sub_domain.element(z_pos); + let z_pos = z_pos * 2; // WIP + let mut f_is: Vec<P> = Vec::new(); f_is.push(p.clone()); - let mut commitments: Vec<F> = Vec::new(); - let mut mts: Vec<MT<F>> = Vec::new(); while f_i1.degree() > 1 { - let alpha_i = F::from(3_u64); // TODO: WIP, defined by Verifier (well, hash transcript) + let alpha_i = F::from(42_u64); // TODO: WIP, defined by Verifier (well, hash transcript) let (fL_i, fR_i) = Self::split(&f_i1);@@ -60,47 +88,71 @@ let aux = DensePolynomial::from_coefficients_slice(fR_i.coeffs());
f_i1 = fL_i.clone() + P::from_coefficients_slice(aux.mul(alpha_i).coeffs()); f_is.push(f_i1.clone()); + let subdomain_evaluations: Vec<F> = cfg_into_iter!(0..eval_sub_domain.size()) + .map(|k| f_i1.evaluate(&eval_sub_domain.element(k))) + .collect(); + // commit to f_{i+1}(x) = fL_i(x) + alpha_i * fR_i(x) - let (cm_i, mt_i) = MT::commit(f_i1.coeffs()); + let (cm_i, mt_i) = MT::commit(&subdomain_evaluations); // commit to the evaluation domain instead commitments.push(cm_i); mts.push(mt_i); + + // prepare next subdomain + sub_order = sub_order / 2; + eval_sub_domain = GeneralEvaluationDomain::new(sub_order).unwrap(); } let (fL_i, fR_i) = Self::split(&f_i1); let constant_fL_l: F = fL_i.coeffs()[0].clone(); let constant_fR_l: F = fR_i.coeffs()[0].clone(); - // TODO this will be a hash from the transcript - // V sets rand z \in \mathbb{F} challenge - let z = F::from(10_u64); - + // evals = {f_i(z^{2^i}), f_i(-z^{2^i})} \forall i \in F_i let mut evals: Vec<F> = Vec::new(); + let mut mtproofs: Vec<Vec<F>> = Vec::new(); // TODO this will be done inside the prev loop, now it is here just for clarity - // evaluate f_i(z^{2^i}) + // evaluate f_i(z^{2^i}), f_i(-z^{2^i}), and open their commitment for i in 0..f_is.len() { - // TODO check usage of .pow(u64) - let z_2i = z.pow([2_u64.pow(i as u32)]); // z^{2^i} + let z_2i = z.pow([2_u64.pow(i as u32)]); // z^{2^i} // TODO check usage of .pow(u64) let neg_z_2i = z_2i.neg(); let eval_i = f_is[i].evaluate(&z_2i); evals.push(eval_i); let eval_i = f_is[i].evaluate(&neg_z_2i); evals.push(eval_i); + + // gen the openings in the commitment to f_i(z^(2^i)) + let mtproof = mts[i].open(F::from(z_pos as u32)); // WIP open to 2^i? + mtproofs.push(mtproof); } - // TODO return also the commitment_proofs - // return: Comm(f_i(x)), f_i(+-z^{2^i}), constant values {f_l^L, f_l^R} - (commitments, evals, [constant_fL_l, constant_fR_l]) + (commitments, mtproofs, evals, [constant_fL_l, constant_fR_l]) } - pub fn verify(commitments: Vec<F>, evals: Vec<F>, constants: [F; 2]) -> bool { - let z = F::from(10_u64); // TODO this will be a hash from the transcript + // verify implements the verification of a FRI-low-degree-testing proof + pub fn verify( + degree: usize, // expected degree + commitments: Vec<F>, + mtproofs: Vec<Vec<F>>, + evals: Vec<F>, + constants: [F; 2], + ) -> bool { + let sub_order = ((degree + 1) / 2) - 1; // TMP, TODO this will depend on rho parameter + let eval_sub_domain: GeneralEvaluationDomain<F> = + GeneralEvaluationDomain::new(sub_order).unwrap(); + // TODO this will be a hash from the transcript + let z_pos = 3; + let z = eval_sub_domain.element(z_pos); + let z_pos = z_pos * 2; - // TODO check commitments.len()==evals.len()/2 + if commitments.len() != (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) { - let alpha_i = F::from(3_u64); // TODO: WIP, defined by Verifier (well, hash transcript) + let alpha_i = F::from(42_u64); // TODO: WIP, defined by Verifier (well, hash transcript) - let z_2i = z.pow([2_u64.pow((i as u32) / 2)]); // z^{2^i} - // take f_i(z^2) from evals + // 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]; // compute f_i^L(z^2), f_i^R(z^2) from the linear combination@@ -113,27 +165,39 @@
// 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] { - println!("\nerr, i={:?}", i); - println!(" next_fi^z2 {:?}", next_fi_z2.to_string()); - println!(" e[i] {:?}", evals[i + 2].to_string()); - panic!("should f_i+1(z^2) == evals.f_i+1(z^2) (=evals[i+2])"); + println!( + "verify step i={}, should f_i+1(z^2) == evals.f_i+1(z^2) (=evals[i+2])", + i + ); + return false; } } // check commitment opening - // TODO + if !MT::verify( + commitments[i_z], + // F::from(i as u32), + F::from(z_pos as u32), + evals[i], + mtproofs[i_z].clone(), + ) { + println!("verify step i={}, MT::verify failed", i); + return false; + } // 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] { - panic!("constant L not equal"); + println!("constant L not equal to the obtained one"); + return false; } if R != constants[1] { - println!("R {:?}\n {:?}", R.to_string(), constants[1].to_string()); - panic!("constant R not equal"); + println!("constant R not equal to the obtained one"); + return false; } } + i_z += 1; } true@@ -156,8 +220,8 @@ let deg = 7;
let p = DensePolynomial::<Fr>::rand(deg, &mut rng); assert_eq!(p.degree(), deg); - type FRIC = FRI<Fr, DensePolynomial<Fr>>; - let (pL, pR) = FRIC::split(&p); + type FRIT = FRI_LDT<Fr, DensePolynomial<Fr>>; + let (pL, pR) = FRIT::split(&p); // check that f(z) == fL(x^2) + x * fR(x^2), for a rand z let z = Fr::rand(&mut rng);@@ -176,14 +240,21 @@ let p = DensePolynomial::<Fr>::rand(deg, &mut rng);
assert_eq!(p.degree(), deg); // println!("p {:?}", p); - type FRIC = FRI<Fr, DensePolynomial<Fr>>; + type FRIT = FRI_LDT<Fr, DensePolynomial<Fr>>; // prover - let (commitments, evals, constvals) = FRIC::prove(&mut rng, &p); + let (commitments, mtproofs, evals, constvals) = FRIT::prove(&mut rng, &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 - 1); + assert_eq!(commitments.len(), log2(p.coeffs().len()) as usize); assert_eq!(evals.len(), 2 * log2(p.coeffs().len()) as usize); - let v = FRIC::verify(commitments, evals, constvals); + let v = FRIT::verify( + // Fr::from(deg as u32), + deg, + commitments, + mtproofs, + evals, + constvals, + ); assert!(v); } }
M
src/merkletree.rs
→
src/merkletree.rs
@@ -105,9 +105,9 @@ path.push(value_bytes[(i / 8) as usize] & (1 << (i % 8)) != 0);
} path } - pub fn gen_proof(&self, index: usize) -> Vec<F> { + pub fn gen_proof(&self, index: F) -> Vec<F> { // start from root, and go down to the index, while getting the siblings at each level - let path = Self::get_path(self.nlevels, F::from(index as u32)); + let path = Self::get_path(self.nlevels, index); // reverse path as we're going from up to down let path_inv = path.iter().copied().rev().collect(); let mut siblings: Vec<F> = Vec::new();@@ -126,9 +126,9 @@ siblings.push(node.left.unwrap().hash);
return Self::go_down(path[1..].to_vec(), *node.right.unwrap(), siblings); } } - pub fn verify(params: &Params<F>, root: F, index: usize, value: F, siblings: Vec<F>) -> bool { + pub fn verify(params: &Params<F>, root: F, index: F, value: F, siblings: Vec<F>) -> bool { let mut h = params.poseidon_hash.hash(&[value]).unwrap(); - let path = Self::get_path(siblings.len() as u32, F::from(index as u32)); + let path = Self::get_path(siblings.len() as u32, index); for i in 0..siblings.len() { if !path[i] { h = params@@ -150,6 +150,12 @@ }
} pub struct MerkleTreePoseidon<F: PrimeField>(MerkleTree<F>); + +pub struct MTProof<F: PrimeField> { + index: F, + siblings: Vec<F>, +} + impl<F: PrimeField> MerkleTreePoseidon<F> { pub fn commit(values: &[F]) -> (F, Self) { let poseidon_params = poseidon_setup_params::<F>(Curve::Bn254, 5, 4);@@ -158,10 +164,10 @@ let params = MerkleTree::setup(&poseidon_hash);
let mt = MerkleTree::new(¶ms, values.to_vec()); (mt.root.hash, MerkleTreePoseidon(mt)) } - pub fn prove(&self, index: usize) -> Vec<F> { + pub fn open(&self, index: F) -> Vec<F> { self.0.gen_proof(index) } - pub fn verify(root: F, index: usize, value: F, siblings: Vec<F>) -> bool { + pub fn verify(root: F, index: F, value: F, siblings: Vec<F>) -> bool { let poseidon_params = poseidon_setup_params::<F>(Curve::Bn254, 5, 4); let poseidon_hash = poseidon::Poseidon::new(poseidon_params); let params = MerkleTree::setup(&poseidon_hash);@@ -258,12 +264,13 @@ "Fp256 \"(10E90845D7A113686E4F2F30D73B315BA891A5DB9A58782F1260C35F99660794)\""
); let index = 3; - let siblings = mt.gen_proof(index); + let index_F = Fr::from(index as u32); + let siblings = mt.gen_proof(index_F); assert!(MerkleTree::verify( ¶ms, mt.root.hash, - index, + index_F, values[index], siblings ));@@ -278,7 +285,7 @@ let params = MerkleTree::setup(&poseidon_hash);
let mut rng = ark_std::test_rng(); - let n_values = 256; + let n_values = 64; let mut values: Vec<Fr> = Vec::new(); for _i in 0..n_values { let v = Fr::rand(&mut rng);@@ -286,14 +293,15 @@ values.push(v);
} let mt = MerkleTree::new(¶ms, values.to_vec()); - assert_eq!(mt.nlevels, 8); + assert_eq!(mt.nlevels, 6); for i in 0..n_values { - let siblings = mt.gen_proof(i); + let i_Fr = Fr::from(i as u32); + let siblings = mt.gen_proof(i_Fr); assert!(MerkleTree::verify( ¶ms, mt.root.hash, - i, + i_Fr, values[i], siblings ));