use ark_ff::PrimeField;
use ark_poly::Polynomial;
use ark_std::collections::BTreeSet;
use ark_std::iterable::Iterable;
use ark_std::{end_timer, start_timer};
use ark_std::{vec, vec::Vec};
use crate::pcs::{Commitment, PCS};
use crate::utils::poly;
use crate::utils::poly::interpolate_evaluate;
use crate::{utils, EuclideanPolynomial, Poly};
pub struct MultipointClaim<F: PrimeField, C: Commitment<F>> {
pub c: C,
pub xs: Vec<F>,
pub ys: Vec<F>,
}
pub trait ShplonkTranscript<F: PrimeField, CS: PCS<F>> {
fn get_gamma(&mut self) -> F;
fn commit_to_q(&mut self, q: &CS::C);
fn get_zeta(&mut self) -> F;
}
pub fn aggregate_polys<F: PrimeField, CS: PCS<F>, T: ShplonkTranscript<F, CS>>(
ck: &CS::CK,
fs: &[Poly<F>],
xss: &[BTreeSet<F>],
transcript: &mut T,
) -> (Poly<F>, F, CS::C) {
assert_eq!(
xss.len(),
fs.len(),
"{} opening sets specified for {} polynomials",
xss.len(),
fs.len()
);
let zs: Vec<_> = xss.iter().map(|xsi| poly::z_of_set(xsi)).collect();
let t_divisions = start_timer!(|| "polynomial divisions");
let (qs, rs): (Vec<_>, Vec<_>) = fs
.iter()
.zip(zs.iter())
.map(|(fi, zi)| fi.divide_with_q_and_r(zi))
.unzip();
end_timer!(t_divisions);
let gamma = transcript.get_gamma();
let q = poly::sum_with_powers(gamma, &qs);
let t_commit =
start_timer!(|| ark_std::format!("commitment to a degree-{} polynomial", q.degree()));
let qc = CS::commit(ck, &q).unwrap();
end_timer!(t_commit);
transcript.commit_to_q(&qc);
let zeta = transcript.get_zeta();
let rs_at_zeta: Vec<_> = rs.iter().map(|ri| ri.evaluate(&zeta)).collect();
let zs_at_zeta: Vec<_> = zs.iter().map(|zi| zi.evaluate(&zeta)).collect();
let ps: Vec<Poly<F>> = fs
.iter()
.zip(rs_at_zeta)
.map(|(fi, ri)| fi - &poly::constant(ri))
.collect();
let (coeffs, normalizer) = get_coeffs(zs_at_zeta, gamma);
let t_combine = start_timer!(|| "linear combination of polynomials");
let l_norm = &poly::sum_with_coeffs(coeffs, &ps) - &(&q * normalizer);
end_timer!(t_combine);
(l_norm, zeta, qc)
}
fn get_coeffs<F: PrimeField>(zs_at_zeta: Vec<F>, gamma: F) -> (Vec<F>, F) {
assert!(!zs_at_zeta.is_empty(), "empty vec");
let normalizer = zs_at_zeta[0];
let mut zs_at_zeta_inv = zs_at_zeta;
ark_ff::batch_inversion(&mut zs_at_zeta_inv);
let coeffs = zs_at_zeta_inv
.iter()
.zip(utils::powers(gamma))
.map(|(zi_inv, gamma_to_i)| gamma_to_i * zi_inv * normalizer)
.collect();
(coeffs, normalizer)
}
pub fn group_by_commitment<F: PrimeField, C: Commitment<F>>(
fcs: &[C],
xss: &Vec<Vec<F>>,
yss: &Vec<Vec<F>>,
) -> Vec<MultipointClaim<F, C>> {
fcs.iter()
.cloned()
.zip(xss.iter().cloned())
.zip(yss.iter().cloned())
.map(|((c, xs), ys)| MultipointClaim { c, xs, ys })
.collect()
}
pub fn aggregate_claims<F: PrimeField, CS: PCS<F>, T: ShplonkTranscript<F, CS>>(
claims: Vec<MultipointClaim<F, CS::C>>,
qc: &CS::C,
onec: &CS::C,
transcript: &mut T,
) -> MultipointClaim<F, CS::C> {
let gamma = transcript.get_gamma();
transcript.commit_to_q(&qc);
let zeta = transcript.get_zeta();
let t_eval = start_timer!(|| "barycentric evaluations");
let (rs_at_zeta, zs_at_zeta): (Vec<_>, Vec<_>) = claims
.iter()
.map(|MultipointClaim { c: _, xs, ys }| interpolate_evaluate(xs, ys, &zeta))
.unzip();
end_timer!(t_eval);
let (mut coeffs, normalizer) = get_coeffs(zs_at_zeta, gamma);
assert!(coeffs[0].is_one());
let agg_r_at_zeta: F = rs_at_zeta
.into_iter()
.zip(coeffs.iter())
.map(|(ri_at_zeta, coeff)| ri_at_zeta * coeff)
.sum();
let mut commitments = claims.into_iter().map(|cl| cl.c).collect::<Vec<_>>();
coeffs.push(-agg_r_at_zeta);
commitments.push(onec.clone());
coeffs.push(-normalizer);
commitments.push(qc.clone());
let t_combine = start_timer!(|| "multiexp");
let lc = CS::C::combine(&coeffs, &commitments);
end_timer!(t_combine);
MultipointClaim {
c: lc,
xs: vec![zeta],
ys: vec![F::zero()],
}
}
#[cfg(test)]
mod tests {
use ark_ff::{One, UniformRand};
use ark_std::format;
use ark_std::iter::FromIterator;
use ark_std::test_rng;
use ark_std::{end_timer, start_timer};
use crate::pcs::IdentityCommitment;
use crate::pcs::PcsParams;
use crate::shplonk::tests::{random_opening, random_xss};
use crate::tests::{BenchField, BenchKzg, TestField, TestKzg, BENCH_DEG_LOG1};
use super::*;
#[test]
fn test_get_coeffs() {
let rng = &mut test_rng();
let zs = (0..10).map(|_| TestField::rand(rng)).collect::<Vec<_>>();
let gamma = TestField::rand(rng);
let (coeffs, _) = get_coeffs(zs.clone(), gamma);
assert_eq!(coeffs.len(), zs.len());
assert!(coeffs[0].is_one());
let gamma = TestField::one();
let (coeffs, normalizer) = get_coeffs(zs.clone(), gamma);
assert!(coeffs.iter().zip(zs).all(|(c, z)| z * c == normalizer));
}
fn _test_aggregation<F: PrimeField, CS: PCS<F>>(d: usize) {
let rng = &mut test_rng();
let t = 8; let max_m = 3;
let params = CS::setup(d, rng);
let (ck, vk) = (params.ck(), params.vk());
let xss = random_xss(rng, t, max_m);
let opening = random_opening::<_, _, CS>(rng, &ck, d, t, xss);
let sets_of_xss: Vec<BTreeSet<F>> = opening
.xss
.iter()
.map(|xs| BTreeSet::from_iter(xs.iter().cloned()))
.collect();
let transcript = &mut (F::rand(rng), F::rand(rng));
let t_aggregate_polys =
start_timer!(|| format!("Aggregate {} degree-{} polynomials", t, d));
let (agg_poly, zeta, agg_proof) =
aggregate_polys::<_, CS, _>(&ck, &opening.fs, &sets_of_xss, transcript);
end_timer!(t_aggregate_polys);
let claims = group_by_commitment(&opening.fcs, &opening.xss, &opening.yss);
let onec = CS::commit(&vk.clone().into(), &poly::constant(F::one())).unwrap();
let t_aggregate_claims = start_timer!(|| format!("Aggregate {} claims", claims.len()));
let agg_claim = aggregate_claims::<_, CS, _>(claims, &agg_proof, &onec, transcript);
end_timer!(t_aggregate_claims);
assert_eq!(CS::commit(&ck, &agg_poly).unwrap(), agg_claim.c);
assert_eq!(zeta, agg_claim.xs[0]);
assert_eq!(agg_poly.evaluate(&zeta), agg_claim.ys[0]);
assert!(agg_claim.ys[0].is_zero());
}
#[test]
fn test_aggregation() {
_test_aggregation::<TestField, IdentityCommitment>(15);
_test_aggregation::<TestField, TestKzg>(15);
}
#[test]
#[ignore]
fn bench_aggregation() {
_test_aggregation::<BenchField, BenchKzg>((1 << BENCH_DEG_LOG1) - 1);
}
}