本文主要解析基于论文《Polynomial Commitments∗》在代码库中的实现细节。 代码的实现流程基于论文《Marlin:Preprocessing zkSNARKs with Universal and Updatable SRS》中的6.1节和B.3。
zexe
的bench-utils
的print-trace
features支持对各个阶段的代码运行效率进行profiling。调用的方法可为:
cargo test end_to_end_test --features print-trace -- --nocaputre
为了便于对比,修改了程序,要求bls12-377
、bls12-381
、mnt6
、sw6
每组曲线都对相同阶(如18)的多项式进行commit。
end_to_end_test::().expect("test failed for bls12-377");
end_to_end_test::().expect("test failed for bls12-381");
end_to_end_test::().expect("test failed for MNT6");
end_to_end_test::().expect("test failed for SW6");
会有类似的运行结果:【由此可知,总体来说bls12-377
的运算性能最高,bls12-377
>bls12-381
>mnt6
>sw6
】【evaluating polynomial的时间不是这样的,可能跟evaluate point有关系。固定在相同的点evaluate 试下。】
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(
degree: usize,
rng: &mut R,
) -> Result {
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 *= β
}
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::(
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::(
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(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) -> 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,
hiding_bound: Option,
rng: Option,
) -> Result {
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::();
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::();
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 PCRandomness for Randomness {
fn empty() -> Self {
Self {
blinding_polynomial: Polynomial::zero(),
}
}
fn rand(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,
point: E::Fr,
randomness: &Self::Randomness,
) -> Result {
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::();
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::();
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 Div for &'b DensePolynomial {
type Output = DensePolynomial;
#[inline]
fn div(self, divisor: &'a DensePolynomial) -> DensePolynomial {
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 {
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 = 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 {
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(
vk: &Self::VerifierKey,
commitments: &[Self::Commitment],
points: &[E::Fr],
values: &[E::Fr],
proofs: &[Self::Proof],
rng: &mut R,
) -> Result {
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 = ::zero();
let mut total_w = ::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节内容:
-
论文《Constant-Size Commitments to Polynomials and Their Applications》: provided protocols to commit to polynomials and then evaluate them at a given point in a verifiable way. Their protocols only require a constant number of commitments but security relies on pairing assumptions。 基于的数学原理为:(论文《Sonic: Zero-Knowledge SNARKs from Linear-Size Universal and Updatable Structured Reference Strings》中的polynomial commitment也是基于此原理。)
-
论文《Efficient Zero-Knowledge Arguments for Arithmetic Circuits in the Discrete Log Setting》: Our polynomial commitment protocol has square root communication complexity but relies solely on the discrete logarithm assumption. 基于的原理是:(将系数拆分为矩阵,每行作为一个vector,将polynomial commitment拆分由vector commitment组成。)
参考资料: [1] 论文《Polynomial Commitments∗》 [2] 代码库:https://github.com/scipr-lab/poly-commit [3] 博客polynomial commitment及实现方式对比 [4] 博客椭圆曲线形式下的Pedersen commitment——vector commitmnt和polynomial commitment