1. 引言
B¨unz 等人2020年论文《proof-carrying data from accumulation schemes》,暂无收录信息。
代码实现:
- https://github.com/scipr-lab/poly-commit 中的ipa_pc
https://github.com/scipr-lab/poly-commit
中的ipa_pc
模块基于Jubjub curve 实现了discrete logarithm based polynomial commitment scheme
P
C
D
L
PC_{DL}
PCDL。
Jubjub 为 twisted Edwards curve,详细信息可参见:
- https://github.com/scipr-lab/zexe/blob/master/algebra/src/ed_on_bls12_381/mod.rs
- https://github.com/zkcrypto/jubjub
2. discrete logarithm based polynomial commitment scheme P C D L PC_{DL} PCDL实现
2.1 基础运算
- Pedersen commitment计算:
/// Create a Pedersen commitment to `scalars` using the commitment key `comm_key`.
/// Optionally, randomize the commitment using `hiding_generator` and `randomizer`.
fn cm_commit(
comm_key: &[G],
scalars: &[G::ScalarField],
hiding_generator: Option<G>,
randomizer: Option<G::ScalarField>,
) -> G::Projective {
let scalars_bigint = ff_fft::cfg_iter!(scalars)
.map(|s| s.into_repr())
.collect::<Vec<_>>();
let mut comm = VariableBaseMSM::multi_scalar_mul(comm_key, &scalars_bigint);
if randomizer.is_some() {
assert!(hiding_generator.is_some());
comm += &hiding_generator.unwrap().mul(randomizer.unwrap());
}
comm
}
- random oracle challenge 计算:
fn compute_random_oracle_challenge(bytes: &[u8]) -> G::ScalarField {
let mut i = 0u64;
let mut challenge = None;
while challenge.is_none() {
let hash_input = algebra_core::to_bytes![bytes, i].unwrap();
let hash = D::digest(&hash_input);
challenge = <G::ScalarField as Field>::from_random_bytes(&hash);
i += 1;
}
challenge.unwrap()
}
- inner product计算:
#[inline]
fn inner_product(l: &[G::ScalarField], r: &[G::ScalarField]) -> G::ScalarField {
ff_fft::cfg_iter!(l).zip(r).map(|(li, ri)| *li * ri).sum()
}
2.2 P C D L PC_{DL} PCDL 算法实现
P C D L PC_{DL} PCDL 详细算法参看博客 proof-carrying data from accumulation schemes学习笔记 第1.5节“基于discrete logarithm 构建的polynomial commitment” 内容。
- P C D L . S e t u p PC_{DL}.Setup PCDL.Setup算法:
fn setup<R: RngCore>(
max_degree: usize,
_rng: &mut R,
) -> Result<Self::UniversalParams, Self::Error> {
// Ensure that max_degree + 1 is a power of 2
let max_degree = (max_degree + 1).next_power_of_two() - 1;
let setup_time = start_timer!(|| format!("Sampling {} generators", max_degree + 3));
let mut generators = Self::sample_generators(max_degree + 3);
end_timer!(setup_time);
let h = generators.pop().unwrap();
let s = generators.pop().unwrap();
let pp = UniversalParams {
comm_key: generators,
h,
s,
};
Ok(pp)
}
/// `UniversalParams` are the universal parameters for the inner product arg scheme.
#[derive(Derivative)]
#[derivative(Default(bound = ""), Clone(bound = ""), Debug(bound = ""))]
pub struct UniversalParams<G: AffineCurve> {
/// The key used to commit to polynomials.
pub comm_key: Vec<G>,
/// Some group generator.
pub h: G,
/// Some group generator specifically used for hiding.
pub s: G,
}
- P C D L . T r i m PC_{DL}.Trim PCDL.Trim算法:
fn trim(
pp: &Self::UniversalParams,
supported_degree: usize,
_enforced_degree_bounds: Option<&[usize]>,
) -> Result<(Self::CommitterKey, Self::VerifierKey), Self::Error> {
// Ensure that supported_degree + 1 is a power of two
let supported_degree = (supported_degree + 1).next_power_of_two() - 1;
if supported_degree > pp.max_degree() {
return Err(Error::TrimmingDegreeTooLarge);
}
let trim_time =
start_timer!(|| format!("Trimming to supported degree of {}", supported_degree));
let ck = CommitterKey {
comm_key: pp.comm_key[0..(supported_degree + 1)].to_vec(),
h: pp.h.clone(),
s: pp.s.clone(),
max_degree: pp.max_degree(),
};
let vk = VerifierKey {
comm_key: pp.comm_key[0..(supported_degree + 1)].to_vec(),
h: pp.h.clone(),
s: pp.s.clone(),
max_degree: pp.max_degree(),
};
end_timer!(trim_time);
Ok((ck, vk))
}
- P C D L . C o m m i t PC_{DL}.Commit PCDL.Commit算法:【注意,此时polynomial degree d d d 可能小于 commitment key中的 supported_degree ,需要引入shifted_comm。】
/// Outputs a commitment to `polynomial`.
fn commit<'a>(
ck: &Self::CommitterKey,
polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<'a, G::ScalarField>>,
rng: Option<&mut dyn RngCore>,
) -> Result<
(
Vec<LabeledCommitment<Self::Commitment>>,
Vec<Self::Randomness>,
),
Self::Error,
> {
let rng = &mut crate::optional_rng::OptionalRng(rng);
let mut comms = Vec::new();
let mut rands = Vec::new();
let commit_time = start_timer!(|| "Committing to polynomials");
for labeled_polynomial in polynomials {
Self::check_degrees_and_bounds(ck.supported_degree(), labeled_polynomial)?;
let polynomial = labeled_polynomial.polynomial();
let label = labeled_polynomial.label();
let hiding_bound = labeled_polynomial.hiding_bound();
let degree_bound = labeled_polynomial.degree_bound();
let commit_time = start_timer!(|| format!(
"Polynomial {} of degree {}, degree bound {:?}, and hiding bound {:?}",
label,
polynomial.degree(),
degree_bound,
hiding_bound,
));
let randomness = if let Some(h) = hiding_bound {
Randomness::rand(h, degree_bound.is_some(), rng)
} else {
Randomness::empty()
};
let comm = Self::cm_commit(
&ck.comm_key[..(polynomial.degree() + 1)],
&polynomial.coeffs,
Some(ck.s),
Some(randomness.rand),
)
.into();
let shifted_comm = degree_bound.map(|d| {
Self::cm_commit(
&ck.comm_key[(ck.supported_degree() - d)..],
&polynomial.coeffs,
Some(ck.s),
randomness.shifted_rand,
)
.into()
});
let commitment = Commitment { comm, shifted_comm };
let labeled_comm = LabeledCommitment::new(label.to_string(), commitment, degree_bound);
comms.push(labeled_comm);
rands.push(randomness);
end_timer!(commit_time);
}
end_timer!(commit_time);
Ok((comms, rands))
}
- P C D L . O p e n PC_{DL}.Open PCDL.Open算法:
fn open<'a>(
ck: &Self::CommitterKey,
labeled_polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<'a, G::ScalarField>>,
commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
point: G::ScalarField,
opening_challenge: G::ScalarField,
rands: impl IntoIterator<Item = &'a Self::Randomness>,
rng: Option<&mut dyn RngCore>,
) -> Result<Self::Proof, Self::Error>
where
Self::Commitment: 'a,
Self::Randomness: 'a,
{
let mut combined_polynomial = Polynomial::zero();
let mut combined_rand = G::ScalarField::zero();
let mut combined_commitment_proj = G::Projective::zero();
let mut has_hiding = false;
let polys_iter = labeled_polynomials.into_iter();
let rands_iter = rands.into_iter();
let comms_iter = commitments.into_iter();
let combine_time = start_timer!(|| "Combining polynomials, randomness, and commitments.");
let mut cur_challenge = opening_challenge;
for (labeled_polynomial, (labeled_commitment, randomness)) in
polys_iter.zip(comms_iter.zip(rands_iter))
{
let label = labeled_polynomial.label();
assert_eq!(labeled_polynomial.label(), labeled_commitment.label());
Self::check_degrees_and_bounds(ck.supported_degree(), labeled_polynomial)?;
let polynomial = labeled_polynomial.polynomial();
let degree_bound = labeled_polynomial.degree_bound();
let hiding_bound = labeled_polynomial.hiding_bound();
let commitment = labeled_commitment.commitment();
combined_polynomial += (cur_challenge, polynomial);
combined_commitment_proj += &commitment.comm.mul(cur_challenge);
if hiding_bound.is_some() {
has_hiding = true;
combined_rand += &(cur_challenge * &randomness.rand);
}
cur_challenge *= &opening_challenge;
let has_degree_bound = degree_bound.is_some();
assert_eq!(
has_degree_bound,
commitment.shifted_comm.is_some(),
"shifted_comm mismatch for {}",
label
);
assert_eq!(
degree_bound,
labeled_commitment.degree_bound(),
"labeled_comm degree bound mismatch for {}",
label
);
if let Some(degree_bound) = degree_bound {
let shifted_polynomial = Self::shift_polynomial(ck, polynomial, degree_bound);
combined_polynomial += (cur_challenge, &shifted_polynomial);
combined_commitment_proj += &commitment.shifted_comm.unwrap().mul(cur_challenge);
if hiding_bound.is_some() {
let shifted_rand = randomness.shifted_rand;
assert!(
shifted_rand.is_some(),
"shifted_rand.is_none() for {}",
label
);
combined_rand += &(cur_challenge * &shifted_rand.unwrap());
}
}
cur_challenge *= &opening_challenge;
}
end_timer!(combine_time);
let combined_v = combined_polynomial.evaluate(point);
// Pad the coefficients to the appropriate vector size
let d = ck.supported_degree();
// `log_d` is ceil(log2 (d + 1)), which is the number of steps to compute all of the challenges
let log_d = algebra_core::log2(d + 1) as usize;
let mut combined_commitment;
let mut hiding_commitment = None;
if has_hiding {
let mut rng = rng.expect("hiding commitments require randomness");
let hiding_time = start_timer!(|| "Applying hiding.");
let mut hiding_polynomial = Polynomial::rand(d, &mut rng);
hiding_polynomial -=
&Polynomial::from_coefficients_slice(&[hiding_polynomial.evaluate(point)]);
let hiding_rand = G::ScalarField::rand(rng);
let hiding_commitment_proj = Self::cm_commit(
ck.comm_key.as_slice(),
hiding_polynomial.coeffs.as_slice(),
Some(ck.s),
Some(hiding_rand),
);
let mut batch = G::Projective::batch_normalization_into_affine(&[
combined_commitment_proj,
hiding_commitment_proj,
]);
hiding_commitment = Some(batch.pop().unwrap());
combined_commitment = batch.pop().unwrap();
let hiding_challenge = Self::compute_random_oracle_challenge(
&algebra_core::to_bytes![
combined_commitment,
point,
combined_v,
hiding_commitment.unwrap()
]
.unwrap(),
);
combined_polynomial += (hiding_challenge, &hiding_polynomial);
combined_rand += &(hiding_challenge * &hiding_rand);
combined_commitment_proj +=
&(hiding_commitment_proj.mul(hiding_challenge) - &ck.s.mul(combined_rand));
end_timer!(hiding_time);
}
let combined_rand = if has_hiding {
Some(combined_rand)
} else {
None
};
let proof_time =
start_timer!(|| format!("Generating proof for degree {} combined polynomial", d + 1));
combined_commitment = combined_commitment_proj.into_affine();
// ith challenge
let mut round_challenge = Self::compute_random_oracle_challenge(
&algebra_core::to_bytes![combined_commitment, point, combined_v].unwrap(),
);
let h_prime = ck.h.mul(round_challenge).into_affine();
// Pads the coefficients with zeroes to get the number of coeff to be d+1
let mut coeffs = combined_polynomial.coeffs;
if coeffs.len() < d + 1 {
for _ in coeffs.len()..(d + 1) {
coeffs.push(G::ScalarField::zero());
}
}
let mut coeffs = coeffs.as_mut_slice();
// Powers of z
let mut z: Vec<G::ScalarField> = Vec::with_capacity(d + 1);
let mut cur_z: G::ScalarField = G::ScalarField::one();
for _ in 0..(d + 1) {
z.push(cur_z);
cur_z *= &point;
}
let mut z = z.as_mut_slice();
// This will be used for transforming the key in each step
let mut key_proj: Vec<G::Projective> = ck.comm_key.iter().map(|x| (*x).into()).collect();
let mut key_proj = key_proj.as_mut_slice();
let mut temp;
// Key for MSM
// We initialize this to capacity 0 initially because we want to use the key slice first
let mut comm_key = &ck.comm_key;
let mut l_vec = Vec::with_capacity(log_d);
let mut r_vec = Vec::with_capacity(log_d);
let mut n = d + 1;
while n > 1 {
let (coeffs_l, coeffs_r) = coeffs.split_at_mut(n / 2);
let (z_l, z_r) = z.split_at_mut(n / 2);
let (key_l, key_r) = comm_key.split_at(n / 2);
let (key_proj_l, _) = key_proj.split_at_mut(n / 2);
let l = Self::cm_commit(key_l, coeffs_r, None, None)
+ &h_prime.mul(Self::inner_product(coeffs_r, z_l));
let r = Self::cm_commit(key_r, coeffs_l, None, None)
+ &h_prime.mul(Self::inner_product(coeffs_l, z_r));
let lr = G::Projective::batch_normalization_into_affine(&[l, r]);
l_vec.push(lr[0]);
r_vec.push(lr[1]);
round_challenge = Self::compute_random_oracle_challenge(
&algebra_core::to_bytes![round_challenge, lr[0], lr[1]].unwrap(),
);
let round_challenge_inv = round_challenge.inverse().unwrap();
ff_fft::cfg_iter_mut!(coeffs_l)
.zip(coeffs_r)
.for_each(|(c_l, c_r)| *c_l += &(round_challenge_inv * &c_r));
ff_fft::cfg_iter_mut!(z_l)
.zip(z_r)
.for_each(|(z_l, z_r)| *z_l += &(round_challenge * &z_r));
ff_fft::cfg_iter_mut!(key_proj_l)
.zip(key_r)
.for_each(|(k_l, k_r)| *k_l += &(k_r.mul(round_challenge)));
coeffs = coeffs_l;
z = z_l;
key_proj = key_proj_l;
temp = G::Projective::batch_normalization_into_affine(key_proj);
comm_key = &temp;
n /= 2;
}
end_timer!(proof_time);
Ok(Proof {
l_vec,
r_vec,
final_comm_key: comm_key[0],
c: coeffs[0],
hiding_comm: hiding_commitment,
rand: combined_rand,
})
}
- P C D L . C h e c k PC_{DL}.Check PCDL.Check算法:
fn check<'a, R: RngCore>(
vk: &Self::VerifierKey,
commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
point: G::ScalarField,
values: impl IntoIterator<Item = G::ScalarField>,
proof: &Self::Proof,
opening_challenge: G::ScalarField,
_rng: &mut R,
) -> Result<bool, Self::Error>
where
Self::Commitment: 'a,
{
let check_time = start_timer!(|| "Checking evaluations");
let d = vk.supported_degree();
// `log_d` is ceil(log2 (d + 1)), which is the number of steps to compute all of the challenges
let log_d = algebra_core::log2(d + 1) as usize;
if proof.l_vec.len() != proof.r_vec.len() || proof.l_vec.len() != log_d {
return Err(Error::IncorrectInputLength(
format!(
"Expected proof vectors to be {:}. Instead, l_vec size is {:} and r_vec size is {:}",
log_d,
proof.l_vec.len(),
proof.r_vec.len()
)
));
}
let check_poly =
Self::succinct_check(vk, commitments, point, values, proof, opening_challenge);
if check_poly.is_none() {
return Ok(false);
}
let check_poly_coeffs = check_poly.unwrap().compute_coeffs();
let final_key = Self::cm_commit(
vk.comm_key.as_slice(),
check_poly_coeffs.as_slice(),
None,
None,
);
if !(final_key - &proof.final_comm_key.into()).is_zero() {
return Ok(false);
}
end_timer!(check_time);
Ok(true)
}
- P C D L . S u c c i n c t C h e c k PC_{DL}.SuccinctCheck PCDL.SuccinctCheck算法:
/// The succinct portion of `PC::check`. This algorithm runs in time
/// O(log d), where d is the degree of the committed polynomials.
fn succinct_check<'a>(
vk: &VerifierKey<G>,
commitments: impl IntoIterator<Item = &'a LabeledCommitment<Commitment<G>>>,
point: G::ScalarField,
values: impl IntoIterator<Item = G::ScalarField>,
proof: &Proof<G>,
opening_challenge: G::ScalarField,
) -> Option<SuccinctCheckPolynomial<G::ScalarField>> {
let check_time = start_timer!(|| "Succinct checking");
let d = vk.supported_degree();
// `log_d` is ceil(log2 (d + 1)), which is the number of steps to compute all of the challenges
let log_d = algebra_core::log2(d + 1) as usize;
let mut combined_commitment_proj = G::Projective::zero();
let mut combined_v = G::ScalarField::zero();
let mut cur_challenge = opening_challenge;
let labeled_commitments = commitments.into_iter();
let values = values.into_iter();
for (labeled_commitment, value) in labeled_commitments.zip(values) {
let commitment = labeled_commitment.commitment();
combined_v += &(cur_challenge * &value);
combined_commitment_proj += &labeled_commitment.commitment().comm.mul(cur_challenge);
cur_challenge *= &opening_challenge;
let degree_bound = labeled_commitment.degree_bound();
assert_eq!(degree_bound.is_some(), commitment.shifted_comm.is_some());
if let Some(degree_bound) = degree_bound {
let shift = point.pow([(vk.supported_degree() - degree_bound) as u64]);
combined_v += &(cur_challenge * &value * &shift);
combined_commitment_proj += &commitment.shifted_comm.unwrap().mul(cur_challenge);
}
cur_challenge *= &opening_challenge;
}
let mut combined_commitment = combined_commitment_proj.into_affine();
assert_eq!(proof.hiding_comm.is_some(), proof.rand.is_some());
if proof.hiding_comm.is_some() {
let hiding_comm = proof.hiding_comm.unwrap();
let rand = proof.rand.unwrap();
let hiding_challenge = Self::compute_random_oracle_challenge(
&algebra_core::to_bytes![combined_commitment, point, combined_v, hiding_comm]
.unwrap(),
);
combined_commitment_proj += &(hiding_comm.mul(hiding_challenge) - &vk.s.mul(rand));
combined_commitment = combined_commitment_proj.into_affine();
}
// Challenge for each round
let mut round_challenges = Vec::with_capacity(log_d);
let mut round_challenge = Self::compute_random_oracle_challenge(
&algebra_core::to_bytes![combined_commitment, point, combined_v].unwrap(),
);
let h_prime = vk.h.mul(round_challenge);
let mut round_commitment_proj = combined_commitment_proj + &h_prime.mul(combined_v);
let l_iter = proof.l_vec.iter();
let r_iter = proof.r_vec.iter();
for (l, r) in l_iter.zip(r_iter) {
round_challenge = Self::compute_random_oracle_challenge(
&algebra_core::to_bytes![round_challenge, l, r].unwrap(),
);
round_challenges.push(round_challenge);
round_commitment_proj +=
&(l.mul(round_challenge.inverse().unwrap()) + &r.mul(round_challenge));
}
let check_poly = SuccinctCheckPolynomial::<G::ScalarField>(round_challenges);
let v_prime = check_poly.evaluate(point) * &proof.c;
let h_prime = h_prime.into_affine();
let check_commitment_elem: G::Projective = Self::cm_commit(
&[proof.final_comm_key.clone(), h_prime],
&[proof.c.clone(), v_prime],
None,
None,
);
if !(round_commitment_proj - &check_commitment_elem).is_zero() {
end_timer!(check_time);
return None;
}
end_timer!(check_time);
Some(check_poly)
}
- P C D L . b a t c h c h e c k PC_{DL}.batch_check PCDL.batchcheck算法:
fn batch_check<'a, R: RngCore>(
vk: &Self::VerifierKey,
commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
query_set: &QuerySet<G::ScalarField>,
values: &Evaluations<G::ScalarField>,
proof: &Self::BatchProof,
opening_challenge: G::ScalarField,
rng: &mut R,
) -> Result<bool, Self::Error>
where
Self::Commitment: 'a,
{
let commitments: BTreeMap<_, _> = commitments.into_iter().map(|c| (c.label(), c)).collect();
let mut query_to_labels_map = BTreeMap::new();
for (label, point) in query_set.iter() {
let labels = query_to_labels_map.entry(point).or_insert(BTreeSet::new());
labels.insert(label);
}
assert_eq!(proof.len(), query_to_labels_map.len());
let mut randomizer = G::ScalarField::one();
let mut combined_check_poly = Polynomial::zero();
let mut combined_final_key = G::Projective::zero();
for ((query, labels), p) in query_to_labels_map.into_iter().zip(proof) {
let lc_time =
start_timer!(|| format!("Randomly combining {} commitments", labels.len()));
let mut comms: Vec<&'_ LabeledCommitment<_>> = Vec::new();
let mut vals = Vec::new();
for label in labels.into_iter() {
let commitment = commitments.get(label).ok_or(Error::MissingPolynomial {
label: label.to_string(),
})?;
let v_i = values
.get(&(label.clone(), *query))
.ok_or(Error::MissingEvaluation {
label: label.to_string(),
})?;
comms.push(commitment);
vals.push(*v_i);
}
let check_poly = Self::succinct_check(
vk,
comms.into_iter(),
*query,
vals.into_iter(),
p,
opening_challenge,
);
if check_poly.is_none() {
return Ok(false);
}
let check_poly =
Polynomial::from_coefficients_vec(check_poly.unwrap().compute_coeffs());
combined_check_poly += (randomizer, &check_poly);
combined_final_key += &p.final_comm_key.into_projective().mul(randomizer);
randomizer = u128::rand(rng).into();
end_timer!(lc_time);
}
let proof_time = start_timer!(|| "Checking batched proof");
let final_key = Self::cm_commit(
vk.comm_key.as_slice(),
combined_check_poly.coeffs.as_slice(),
None,
None,
);
if !(final_key - &combined_final_key).is_zero() {
return Ok(false);
}
end_timer!(proof_time);
Ok(true)
}