|
|
@ -14,11 +14,22 @@ use ark_poly::{ |
|
|
|
|
|
|
|
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 @@ impl, H: Hash> FRI_LDT { |
|
|
|
}
|
|
|
|
|
|
|
|
fn split(p: &P) -> (P, P) {
|
|
|
|
// 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");
|
|
|
|
}
|
|
|
|
// 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 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 @@ impl, H: Hash> FRI_LDT { |
|
|
|
}
|
|
|
|
|
|
|
|
// 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 @@ impl, H: Hash> FRI_LDT { |
|
|
|
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 @@ impl, H: Hash> FRI_LDT { |
|
|
|
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 @@ impl, H: Hash> FRI_LDT { |
|
|
|
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 @@ impl, H: Hash> FRI_LDT { |
|
|
|
|
|
|
|
// 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 @@ impl, H: Hash> FRI_LDT { |
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 @@ mod tests { |
|
|
|
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 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 v = FRID::verify(deg, commitments, mtproofs, evals, constvals);
|
|
|
|
let claimed_y = proof.claimed_y.clone(); // WIP
|
|
|
|
let v = PCS::verify(commitment, proof, r, claimed_y);
|
|
|
|
assert!(v);
|
|
|
|
}
|
|
|
|
}
|