Polynomial Commitments代码实现【1】——scipr-lab/poly-commit(含不同曲线性能对比)

1 理论背景

本文主要解析基于论文《Polynomial Commitments∗》在代码库中的实现细节。
代码的实现流程基于论文《Marlin:Preprocessing zkSNARKs with Universal and Updatable SRS》中的6.1节和B.3。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2 相应代码实现

2.0 profiling不同曲线性能对比

zexebench-utilsprint-trace features支持对各个阶段的代码运行效率进行profiling。调用的方法可为:

cargo test end_to_end_test --features print-trace -- --nocaputre

为了便于对比,修改了程序,要求bls12-377bls12-381mnt6sw6每组曲线都对相同阶(如18)的多项式进行commit。

		end_to_end_test::<_, KZG_Bls12_377>().expect("test failed for bls12-377");
        end_to_end_test::<_, KZG_Bls12_381>().expect("test failed for bls12-381");
        end_to_end_test::<_, KZG_MNT6>().expect("test failed for MNT6");
        end_to_end_test::<_, KZG_SW6>().expect("test failed for SW6");

会有类似的运行结果:【由此可知,总体来说bls12-377的运算性能最高,bls12-377>bls12-381>mnt6>sw6】【evaluating polynomial的时间不是这样的,可能跟evaluate point有关系。固定在相同的点evaluate 试下。】
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.1 setup代码实现

 let (ck, vk) = SinglePC::setup(degree, rng)?;

https://github.com/scipr-lab/poly-commit/blob/master/src/single_pc/kzg10.rs中,setup代码实现流程如下:

 	/// Constructs public parameters when given as input the maximum degree `degree`
    /// for the polynomial commitment scheme.
    fn setup<R: RngCore>(
        degree: usize,
        rng: &mut R,
    ) -> Result<(Self::CommitterKey, Self::VerifierKey), Self::Error> {
        if degree < 1 {
            return Err(Error::UnsupportedDegree);
        }
        let setup_time = start_timer!(|| format!("Started KZG10::Setup with degree {}", degree));
        let beta = E::Fr::rand(rng);
        let g = E::G1Projective::rand(rng);
        let gamma_g = E::G1Projective::rand(rng);
        let h = E::G2Projective::rand(rng);

        let mut powers_of_beta = vec![E::Fr::one()];
        let mut cur = beta;
        // TODO: can optimize by computing
        // 1, beta, beta^2, beta^3, ..., beta^d and using those to compute arbitrary
        // powers of beta faster via a bit decomposition of each scalar.
        // But that seems like it would be slower? log(D) additions instead of
        // one multiplication.
        // Anyway it shouldn't be a bottleneck?
        // If we use MPC to run the setup, we can consider this optimization.
        for _ in 0..degree {
            powers_of_beta.push(cur);
            cur *= &beta;
        }

        let window_size = FixedBaseMSM::get_mul_window_size(degree + 1);

        let scalar_bits = E::Fr::size_in_bits();
        let g_time = start_timer!(|| "Generating powers of G");
        let g_table = FixedBaseMSM::get_window_table(scalar_bits, window_size, g);
        let mut powers_of_g = FixedBaseMSM::multi_scalar_mul::<E::G1Projective>(
            scalar_bits,
            window_size,
            &g_table,
            &powers_of_beta,
        );
        end_timer!(g_time);
        let gamma_g_time = start_timer!(|| "Generating powers of gamma * G");
        let gamma_g_table = FixedBaseMSM::get_window_table(scalar_bits, window_size, gamma_g);
        let mut powers_of_gamma_g = FixedBaseMSM::multi_scalar_mul::<E::G1Projective>(
            scalar_bits,
            window_size,
            &gamma_g_table,
            &powers_of_beta,
        );
        end_timer!(gamma_g_time);
        E::G1Projective::batch_normalization(powers_of_g.as_mut_slice());
        E::G1Projective::batch_normalization(powers_of_gamma_g.as_mut_slice());

        let ck = CommitterKey {
            powers_of_g: powers_of_g.into_iter().map(|e| e.into_affine()).collect(),
            powers_of_gamma_g: powers_of_gamma_g
                .into_iter()
                .map(|e| e.into_affine())
                .collect(),
            degree,
        };

        let beta_h = h.mul(&beta).into_affine();
        let h = h.into_affine();
        let prepared_h = h.prepare();
        let prepared_beta_h = beta_h.prepare();
        let vk = VerifierKey {
            g: g.into_affine(),
            gamma_g: gamma_g.into_affine(),
            h,
            beta_h,
            prepared_h,
            prepared_beta_h,
            degree,
        };
        end_timer!(setup_time);
        Ok((ck, vk))
    }

2.2 随机多项式生成

let p = Polynomial::rand(degree, rng);

pub use ff_fft::DensePolynomial as Polynomial;

https://github.com/scipr-lab/zexe/blob/master/ff-fft/src/polynomial/dense.rs中的

	/// Outputs a polynomial of degree `d` where each coefficient is sampled uniformly at random
    /// from the field `F`.
    pub fn rand<R: Rng>(d: usize, rng: &mut R) -> Self {
        let mut random_coeffs = Vec::new();
        for _ in 0..(d + 1) {
            random_coeffs.push(F::rand(rng));
        }
        Self::from_coefficients_vec(random_coeffs)
    }
	/// Constructs a new polynomial from a list of coefficients.
    pub fn from_coefficients_vec(mut coeffs: Vec<F>) -> Self {
        // While there are zeros at the end of the coefficient vector, pop them off.
        while coeffs.last().map_or(false, |c| c.is_zero()) {
            coeffs.pop();
        }
        // Check that either the coefficients vec is empty or that the last coeff is non-zero.
        assert!(coeffs.last().map_or(true, |coeff| !coeff.is_zero()));

        Self { coeffs }
    }

2.3 commit代码实现

/// Outputs a commitment to `polynomial`.
    fn commit(
        ck: &Self::CommitterKey,
        polynomial: &Polynomial<E::Fr>,
        hiding_bound: Option<usize>,
        rng: Option<&mut dyn RngCore>,
    ) -> Result<(Self::Commitment, Self::Randomness), Error> {
        Error::check_degree(polynomial.degree(), ck.max_degree())?;

        let commit_time = start_timer!(|| format!(
            "Committing to polynomial of degree {} with hiding_bound: {:?}",
            polynomial.degree(),
            hiding_bound,
        ));

        let mut skip_zeros = 0;
        while polynomial.coeffs[skip_zeros].is_zero() && skip_zeros < polynomial.coeffs.len() {
            skip_zeros += 1;
        }
        let from_mont_repr_time =
            start_timer!(|| "Converting plaintext polynomial from Montgomery repr");
        let plain_coeffs = polynomial.coeffs[skip_zeros..]
            .par_iter()
            .map(|s| s.into_repr())
            .collect::<Vec<_>>();
        end_timer!(from_mont_repr_time);

        let msm_time = start_timer!(|| "MSM to compute commitment to plaintext poly");
        let mut commitment =
            VariableBaseMSM::multi_scalar_mul(&ck.powers_of_g[skip_zeros..], &plain_coeffs);
        end_timer!(msm_time);

        let mut randomness = Randomness::empty();
        if let Some(hiding_degree) = hiding_bound {
            let mut rng = rng.ok_or(Error::MissingRng)?;
            let sample_random_poly_time = start_timer!(|| format!(
                "Sampling a random polynomial of degree {}",
                hiding_degree
            ));

            randomness = Randomness::rand(hiding_degree, &mut rng);
            if randomness.blinding_polynomial.degree() > ck.max_degree() {
                eprintln!("The hiding bound is too large for the commitment key.");
                Err(Error::PolynomialDegreeTooLarge { 
                    poly_degree: randomness.blinding_polynomial.degree(),
                    max_degree: ck.max_degree(), 
                })?;
            }
            end_timer!(sample_random_poly_time);
        }

        let from_mont_repr_time =
            start_timer!(|| "Converting random polynomial from Montgomery repr");
        let random_ints = randomness
            .blinding_polynomial
            .coeffs
            .par_iter()
            .map(|s: &E::Fr| s.into_repr())
            .collect::<Vec<_>>();
        end_timer!(from_mont_repr_time);

        let msm_time = start_timer!(|| "MSM to compute commitment to random poly");
        let random_commitment =
            VariableBaseMSM::multi_scalar_mul(&ck.powers_of_gamma_g, random_ints.as_slice())
                .into_affine();
        end_timer!(msm_time);

        commitment.add_assign_mixed(&random_commitment);

        end_timer!(commit_time);
        Ok((Commitment(commitment.into()), randomness))
    }
impl<E: PairingEngine> PCRandomness for Randomness<E> {
    fn empty() -> Self {
        Self {
            blinding_polynomial: Polynomial::zero(),
        }
    }

    fn rand<R: RngCore>(d: usize, rng: &mut R) -> Self {
        let mut randomness = Randomness::empty();
        randomness.blinding_polynomial = Polynomial::rand(d + 1, rng);
        randomness
    }
}

2.4 选取evaluation point对多项式进行evaluate

let point = F::rand(rng);
let value = p.evaluate(point);
	/// Evaluates `self` at the given `point` in the field.
    pub fn evaluate(&self, point: F) -> F {
        if self.is_zero() {
            return F::zero();
        }
        let mut powers_of_point = vec![F::one()];
        let mut cur = point;
        for _ in 0..self.degree() {
            powers_of_point.push(cur);
            cur *= &point;
        }
        assert_eq!(powers_of_point.len(), self.coeffs.len());
        let zero = F::zero();
        powers_of_point
            .into_par_iter()
            .zip(&self.coeffs)
            .map(|(power, coeff)| power * coeff)
            .reduce(|| zero, |a, b| a + &b)
    }

2.5 open代码实现

 let proof = SinglePC::open(&ck, &p, point, &rand)?;
	/// On input a polynomial `p` and a point `point`, outputs a proof for the same.
    fn open(
        ck: &Self::CommitterKey,
        p: &Polynomial<E::Fr>,
        point: E::Fr,
        randomness: &Self::Randomness,
    ) -> Result<Self::Proof, Error> {
        Error::check_degree(p.degree(), ck.max_degree())?;
        let open_time = start_timer!(|| format!("Opening polynomial of degree {}", p.degree()));

        let eval_time = start_timer!(|| "Evaluating polynomial");
        let value = p.evaluate(point);
        end_timer!(eval_time);

        let witness_time = start_timer!(|| "Computing witness polynomial");
        let witness_polynomial = &(p - &Polynomial::from_coefficients_vec(vec![value]))
            / &Polynomial::from_coefficients_vec(vec![-point, E::Fr::one()]);
        end_timer!(witness_time);

        let convert_time = start_timer!(|| "Converting witness polynomial from Montgomery repr");
        let witness_coeffs = witness_polynomial
            .coeffs
            .into_par_iter()
            .map(|s| s.into_repr())
            .collect::<Vec<_>>();
        end_timer!(convert_time);

        let witness_comm_time = start_timer!(|| "Computing commitment to witness polynomial");
        let mut w = VariableBaseMSM::multi_scalar_mul(&ck.powers_of_g, &witness_coeffs);
        end_timer!(witness_comm_time);

        let mut random_v = E::Fr::zero();
        if randomness.is_hiding() {
            let random_p = &randomness.blinding_polynomial;

            let rand_eval_time = start_timer!(|| "Evaluating random polynomial");
            let random_value = random_p.evaluate(point);
            end_timer!(rand_eval_time);

            let witness_time = start_timer!(|| "Computing random witness polynomial");
            let random_witness_polynomial = &(random_p
                - &Polynomial::from_coefficients_vec(vec![random_value]))
                / &Polynomial::from_coefficients_vec(vec![-point, E::Fr::one()]);
            end_timer!(witness_time);

            let random_witness_coeffs = random_witness_polynomial
                .coeffs
                .into_par_iter()
                .map(|s| s.into_repr())
                .collect::<Vec<_>>();

            let witness_comm_time =
                start_timer!(|| "Computing commitment to random witness polynomial");
            w += &VariableBaseMSM::multi_scalar_mul(&ck.powers_of_gamma_g, &random_witness_coeffs);
            end_timer!(witness_comm_time);
            random_v = random_value;
        }

        end_timer!(open_time);

        Ok(Proof {
            w: w.into_affine(),
            random_v,
        })
    }

// 多项式除法的实现如下:

impl<'a, 'b, F: Field> Div<&'a DensePolynomial<F>> for &'b DensePolynomial<F> {
    type Output = DensePolynomial<F>;

    #[inline]
    fn div(self, divisor: &'a DensePolynomial<F>) -> DensePolynomial<F> {
        let a: DenseOrSparsePolynomial<_> = self.into();
        let b: DenseOrSparsePolynomial<_> = divisor.into();
        a.divide_with_q_and_r(&b).expect("division failed").0
    }
}

	/// Divide self by another (sparse or dense) polynomial, and returns the quotient and remainder.
    pub fn divide_with_q_and_r(&self, divisor: &Self) -> Option<(DensePolynomial<F>, DensePolynomial<F>)> {
        if self.is_zero() {
            Some((DensePolynomial::zero(), DensePolynomial::zero()))
        } else if divisor.is_zero() {
            panic!("Dividing by zero polynomial")
        } else if self.degree() < divisor.degree() {
            Some((DensePolynomial::zero(), self.clone().into()))
        } else {
            // Now we know that self.degree() >= divisor.degree();
            let mut quotient = vec![F::zero(); self.degree() - divisor.degree() + 1];
            let mut remainder: DensePolynomial<F> = self.clone().into();
            // Can unwrap here because we know self is not zero.
            let divisor_leading_inv = divisor.leading_coefficient().unwrap().inverse().unwrap();
            while !remainder.is_zero() && remainder.degree() >= divisor.degree() {
                let cur_q_coeff = *remainder.coeffs.last().unwrap() * &divisor_leading_inv;
                let cur_q_degree = remainder.degree() - divisor.degree();
                quotient[cur_q_degree] = cur_q_coeff;

                for (i, div_coeff) in divisor.iter_with_index() {
                    remainder[cur_q_degree + i] -= &(cur_q_coeff * &div_coeff);
                }
                while let Some(true) = remainder.coeffs.last().map(|c| c.is_zero()) {
                    remainder.coeffs.pop();
                }
            }
            Some((DensePolynomial::from_coefficients_vec(quotient), remainder))
        }
    }

2.6 check代码实现

	assert!(
                SinglePC::check(&vk, &comm, point, value, &proof)?,
                "proof was incorrect for max_degree = {}, polynomial_degree = {}, hiding_bound = {:?}",
                degree,
                p.degree(),
                hiding_bound,
            );
	/// Verifies that `value` is the evaluation at `x` of the polynomial
    /// committed inside `comm`.
    fn check(
        vk: &Self::VerifierKey,
        comm: &Self::Commitment,
        point: E::Fr,
        value: E::Fr,
        proof: &Self::Proof,
    ) -> Result<bool, Error> {
        let check_time = start_timer!(|| "Checking evaluation");
        let inner = comm.0.into_projective()
            - &vk.g.into_projective().mul(&value)
            - &vk.gamma_g.into_projective().mul(&proof.random_v);
        let lhs = E::pairing(inner, vk.h);

        let inner = vk.beta_h.into_projective() - &vk.h.into_projective().mul(&point);
        let rhs = E::pairing(proof.w, inner);

        end_timer!(check_time, || format!("Result: {}", lhs == rhs));
        Ok(lhs == rhs)
    }

2.7 batch_check代码实现

在这里插入图片描述

	fn batch_check<R: RngCore>(
        vk: &Self::VerifierKey,
        commitments: &[Self::Commitment],
        points: &[E::Fr],
        values: &[E::Fr],
        proofs: &[Self::Proof],
        rng: &mut R,
    ) -> Result<bool, Self::Error> {
        let check_time =
            start_timer!(|| format!("Checking {} evaluation proofs", commitments.len()));
        let g = vk.g.into_projective();
        let gamma_g = vk.gamma_g.into_projective();

        let mut total_c = <E::G1Projective as ProjectiveCurve>::zero();
        let mut total_w = <E::G1Projective as ProjectiveCurve>::zero();

        let combination_time = start_timer!(|| "Combining commitments and proofs");
        let mut randomizer = E::Fr::one();
        // Instead of multiplying g and gamma_g in each turn, we simply accumulate
        // their coefficients and perform a final multiplication at the end.
        let mut g_multiplier = E::Fr::zero();
        let mut gamma_g_multiplier = E::Fr::zero();
        for (((c, z), v), proof) in commitments.iter().zip(points).zip(values).zip(proofs) {
            let mut c = c.0.into_projective();
            let w = proof.w.into_projective();
            c += &w.mul(z);
            g_multiplier += &(randomizer * &v);
            gamma_g_multiplier += &(randomizer * &proof.random_v);
            total_c += &c.mul(&randomizer);
            total_w += &w.mul(&randomizer);
            // We don't need to sample randomizers from the full field,
            // only from 128-bit strings.
            randomizer = u128::rand(rng).into();
        }
        total_c -= &g.mul(&g_multiplier);
        total_c -= &gamma_g.mul(&gamma_g_multiplier);
        end_timer!(combination_time);

        let to_affine_time = start_timer!(|| "Converting results to affine for pairing");
        let mut to_affine = [-total_w, total_c];
        E::G1Projective::batch_normalization(&mut to_affine);
        let [total_w, total_c] = to_affine;
        let total_w = total_w.into_affine();
        let total_c = total_c.into_affine();
        end_timer!(to_affine_time);

        let pairing_time = start_timer!(|| "Performing product of pairings");
        let result = E::product_of_pairings(&[
            (&total_w.prepare(), &vk.prepared_beta_h),
            (&total_c.prepare(), &vk.prepared_h),
        ]) == E::Fqk::one();
        end_timer!(pairing_time);
        end_timer!(check_time, || format!("Result: {}", result));
        Ok(result)
    }

另关注下,Halo中polynomial commitment的实现细节。

借用博客polynomial commitment及实现方式对比3.2节内容:

参考资料:
[1] 论文《Polynomial Commitments∗
[2] 代码库:https://github.com/scipr-lab/poly-commit
[3] 博客polynomial commitment及实现方式对比
[4] 博客椭圆曲线形式下的Pedersen commitment——vector commitmnt和polynomial commitment

发布了154 篇原创文章 · 获赞 13 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/mutourend/article/details/102822861