From 1325a1ee143962d54f50c808348581c6ae8d6b23 Mon Sep 17 00:00:00 2001
From: William Lin <31808623+Will-Lin4@users.noreply.github.com>
Date: Sat, 6 Jun 2020 12:32:03 -0700
Subject: [PATCH] PC Scheme based on Inner Product Arguments (#23)

Co-Authored-By: Pratyush Mishra <pratyushmishra@berkeley.edu>
---
 Cargo.toml                                    |    4 +-
 src/data_structures.rs                        |    3 +-
 src/error.rs                                  |    6 +-
 src/ipa_pc/data_structures.rs                 |  257 ++++
 src/ipa_pc/mod.rs                             | 1088 +++++++++++++++++
 src/kzg10/data_structures.rs                  |    5 +-
 src/kzg10/mod.rs                              |    8 +-
 src/kzg10/optional_rng.rs                     |   31 -
 src/lib.rs                                    |  169 ++-
 .../data_structures.rs                        |    2 +-
 src/{marlin_kzg10 => marlin_pc}/mod.rs        |   87 +-
 src/optional_rng.rs                           |   52 +
 .../data_structures.rs                        |    0
 src/{sonic_kzg10 => sonic_pc}/mod.rs          |   82 +-
 14 files changed, 1670 insertions(+), 124 deletions(-)
 create mode 100644 src/ipa_pc/data_structures.rs
 create mode 100644 src/ipa_pc/mod.rs
 delete mode 100644 src/kzg10/optional_rng.rs
 rename src/{marlin_kzg10 => marlin_pc}/data_structures.rs (98%)
 rename src/{marlin_kzg10 => marlin_pc}/mod.rs (92%)
 create mode 100644 src/optional_rng.rs
 rename src/{sonic_kzg10 => sonic_pc}/data_structures.rs (100%)
 rename src/{sonic_kzg10 => sonic_pc}/mod.rs (92%)

diff --git a/Cargo.toml b/Cargo.toml
index 8d8aee4..d78d02e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -24,12 +24,14 @@ algebra-core = { git = "https://github.com/scipr-lab/zexe/", default-features =
 ff-fft = { git = "https://github.com/scipr-lab/zexe/", default-features = false }
 bench-utils = { git = "https://github.com/scipr-lab/zexe/" }
 rand_core = { version = "0.5", default-features = false }
+digest = "0.8"
 rayon = { version = "1", optional = true }
 derivative = { version = "2", features = [ "use_core" ] }
 
 [dev-dependencies]
 rand = { version = "0.7", default-features = false }
-algebra = { git = "https://github.com/scipr-lab/zexe/", default-features = false, features = ["full"] }
+algebra = { git = "https://github.com/scipr-lab/zexe/", default-features = false, features = ["jubjub", "bls12_381", "bls12_377"] }
+blake2 = { version = "0.8", default-features = false }
 
 [profile.release]
 opt-level = 3
diff --git a/src/data_structures.rs b/src/data_structures.rs
index 108bcc9..df25cd1 100644
--- a/src/data_structures.rs
+++ b/src/data_structures.rs
@@ -101,6 +101,7 @@ impl<'a, F: Field> LabeledPolynomial<'a, F> {
             label,
             polynomial: Cow::Owned(polynomial),
             degree_bound,
+
             hiding_bound,
         }
     }
@@ -174,7 +175,7 @@ impl<C: PCCommitment> LabeledCommitment<C> {
         &self.label
     }
 
-    /// Retrieve the polynomial from `self`.
+    /// Retrieve the commitment from `self`.
     pub fn commitment(&self) -> &C {
         &self.commitment
     }
diff --git a/src/error.rs b/src/error.rs
index 2232c7b..5bef28f 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -79,6 +79,9 @@ pub enum Error {
 
     /// The inputs to `commit`, `open` or `verify` had incorrect lengths.
     IncorrectInputLength(String),
+
+    /// The commitment was generated incorrectly, tampered with, or doesn't support the polynomial.
+    MalformedCommitment(String),
 }
 
 impl core::fmt::Display for Error {
@@ -151,7 +154,8 @@ impl core::fmt::Display for Error {
                  supported degree ({:?})",
                 degree_bound, label, poly_degree, supported_degree
             ),
-            Error::IncorrectInputLength(err) => write!(f, "{}", err)
+            Error::IncorrectInputLength(err) => write!(f, "{}", err),
+            Error::MalformedCommitment(err) => write!(f, "{}", err)
         }
     }
 }
diff --git a/src/ipa_pc/data_structures.rs b/src/ipa_pc/data_structures.rs
new file mode 100644
index 0000000..d041124
--- /dev/null
+++ b/src/ipa_pc/data_structures.rs
@@ -0,0 +1,257 @@
+use crate::*;
+use crate::{PCCommitterKey, PCVerifierKey, Vec};
+use algebra_core::{AffineCurve, Field, ToBytes, UniformRand, Zero};
+use rand_core::RngCore;
+
+/// `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,
+}
+
+impl<G: AffineCurve> PCUniversalParams for UniversalParams<G> {
+    fn max_degree(&self) -> usize {
+        self.comm_key.len() - 1
+    }
+}
+
+/// `CommitterKey` is used to commit to, and create evaluation proofs for, a given
+/// polynomial.
+#[derive(Derivative)]
+#[derivative(
+    Default(bound = ""),
+    Hash(bound = ""),
+    Clone(bound = ""),
+    Debug(bound = "")
+)]
+pub struct CommitterKey<G: AffineCurve> {
+    /// The key used to commit to polynomials.
+    pub comm_key: Vec<G>,
+
+    /// A random group generator.
+    pub h: G,
+
+    /// A random group generator that is to be used to make
+    /// a commitment hiding.
+    pub s: G,
+
+    /// The maximum degree supported by the parameters
+    /// this key was derived from.
+    pub max_degree: usize,
+}
+
+impl<G: AffineCurve> PCCommitterKey for CommitterKey<G> {
+    fn max_degree(&self) -> usize {
+        self.max_degree
+    }
+    fn supported_degree(&self) -> usize {
+        self.comm_key.len() - 1
+    }
+}
+
+/// `VerifierKey` is used to check evaluation proofs for a given commitment.
+pub type VerifierKey<G> = CommitterKey<G>;
+
+impl<G: AffineCurve> PCVerifierKey for VerifierKey<G> {
+    fn max_degree(&self) -> usize {
+        self.max_degree
+    }
+
+    fn supported_degree(&self) -> usize {
+        self.comm_key.len() - 1
+    }
+}
+
+/// Commitment to a polynomial that optionally enforces a degree bound.
+#[derive(Derivative)]
+#[derivative(
+    Default(bound = ""),
+    Hash(bound = ""),
+    Clone(bound = ""),
+    Copy(bound = ""),
+    Debug(bound = ""),
+    PartialEq(bound = ""),
+    Eq(bound = "")
+)]
+pub struct Commitment<G: AffineCurve> {
+    /// A Pedersen commitment to the polynomial.
+    pub comm: G,
+
+    /// A Pedersen commitment to the shifted polynomial.
+    /// This is `none` if the committed polynomial does not
+    /// enforce a strict degree bound.
+    pub shifted_comm: Option<G>,
+}
+
+impl<G: AffineCurve> PCCommitment for Commitment<G> {
+    #[inline]
+    fn empty() -> Self {
+        Commitment {
+            comm: G::zero(),
+            shifted_comm: None,
+        }
+    }
+
+    fn has_degree_bound(&self) -> bool {
+        false
+    }
+
+    fn size_in_bytes(&self) -> usize {
+        algebra_core::to_bytes![G::zero()].unwrap().len() / 2
+    }
+}
+
+impl<G: AffineCurve> ToBytes for Commitment<G> {
+    #[inline]
+    fn write<W: algebra_core::io::Write>(&self, mut writer: W) -> algebra_core::io::Result<()> {
+        self.comm.write(&mut writer)?;
+        let shifted_exists = self.shifted_comm.is_some();
+        shifted_exists.write(&mut writer)?;
+        self.shifted_comm
+            .as_ref()
+            .unwrap_or(&G::zero())
+            .write(&mut writer)
+    }
+}
+
+/// `Randomness` hides the polynomial inside a commitment and is outputted by `InnerProductArg::commit`.
+#[derive(Derivative)]
+#[derivative(
+    Default(bound = ""),
+    Hash(bound = ""),
+    Clone(bound = ""),
+    Debug(bound = ""),
+    PartialEq(bound = ""),
+    Eq(bound = "")
+)]
+pub struct Randomness<G: AffineCurve> {
+    /// Randomness is some scalar field element.
+    pub rand: G::ScalarField,
+
+    /// Randomness applied to the shifted commitment is some scalar field element.
+    pub shifted_rand: Option<G::ScalarField>,
+}
+
+impl<G: AffineCurve> PCRandomness for Randomness<G> {
+    fn empty() -> Self {
+        Self {
+            rand: G::ScalarField::zero(),
+            shifted_rand: None,
+        }
+    }
+
+    fn rand<R: RngCore>(_hiding_bound: usize, has_degree_bound: bool, rng: &mut R) -> Self {
+        let rand = G::ScalarField::rand(rng);
+        let shifted_rand = if has_degree_bound {
+            Some(G::ScalarField::rand(rng))
+        } else {
+            None
+        };
+
+        Self { rand, shifted_rand }
+    }
+}
+
+/// `Proof` is an evaluation proof that is output by `InnerProductArg::open`.
+#[derive(Derivative)]
+#[derivative(
+    Default(bound = ""),
+    Hash(bound = ""),
+    Clone(bound = ""),
+    Debug(bound = "")
+)]
+pub struct Proof<G: AffineCurve> {
+    /// Vector of left elements for each of the log_d iterations in `open`
+    pub l_vec: Vec<G>,
+
+    /// Vector of right elements for each of the log_d iterations within `open`
+    pub r_vec: Vec<G>,
+
+    /// Committer key from the last iteration within `open`
+    pub final_comm_key: G,
+
+    /// Coefficient from the last iteration within withinopen`
+    pub c: G::ScalarField,
+
+    /// Commitment to the blinding polynomial.
+    pub hiding_comm: Option<G>,
+
+    /// Linear combination of all the randomness used for commitments
+    /// to the opened polynomials, along with the randomness used for the
+    /// commitment to the hiding polynomial.
+    pub rand: Option<G::ScalarField>,
+}
+
+impl<G: AffineCurve> PCProof for Proof<G> {
+    fn size_in_bytes(&self) -> usize {
+        algebra_core::to_bytes![self].unwrap().len()
+    }
+}
+
+impl<G: AffineCurve> ToBytes for Proof<G> {
+    #[inline]
+    fn write<W: algebra_core::io::Write>(&self, mut writer: W) -> algebra_core::io::Result<()> {
+        self.l_vec.write(&mut writer)?;
+        self.r_vec.write(&mut writer)?;
+        self.final_comm_key.write(&mut writer)?;
+        self.c.write(&mut writer)?;
+        self.hiding_comm
+            .as_ref()
+            .unwrap_or(&G::zero())
+            .write(&mut writer)?;
+        self.rand
+            .as_ref()
+            .unwrap_or(&G::ScalarField::zero())
+            .write(&mut writer)
+    }
+}
+
+/// `SuccinctCheckPolynomial` is a succinctly-representated polynomial
+/// generated from the `log_d` random oracle challenges generated in `open`.
+/// It has the special property that can be evaluated in `O(log_d)` time.
+pub struct SuccinctCheckPolynomial<F: Field>(pub Vec<F>);
+
+impl<F: Field> SuccinctCheckPolynomial<F> {
+    /// Computes the coefficients of the underlying degree `d` polynomial.
+    pub fn compute_coeffs(&self) -> Vec<F> {
+        let challenges = &self.0;
+        let log_d = challenges.len();
+
+        let mut coeffs = vec![F::one(); 1 << log_d];
+        for (i, challenge) in challenges.iter().enumerate() {
+            let i = i + 1;
+            let elem_degree = 1 << (log_d - i);
+            for start in (elem_degree..coeffs.len()).step_by(elem_degree * 2) {
+                for offset in 0..elem_degree {
+                    coeffs[start + offset] *= challenge;
+                }
+            }
+        }
+
+        coeffs
+    }
+
+    /// Evaluate `self` at `point` in time `O(log_d)`.
+    pub fn evaluate(&self, point: F) -> F {
+        let challenges = &self.0;
+        let log_d = challenges.len();
+
+        let mut product = F::one();
+        for (i, challenge) in challenges.iter().enumerate() {
+            let i = i + 1;
+            let elem_degree: u64 = (1 << (log_d - i)) as u64;
+            let elem = point.pow([elem_degree]);
+            product *= &(F::one() + &(elem * challenge));
+        }
+
+        product
+    }
+}
diff --git a/src/ipa_pc/mod.rs b/src/ipa_pc/mod.rs
new file mode 100644
index 0000000..8d4f20a
--- /dev/null
+++ b/src/ipa_pc/mod.rs
@@ -0,0 +1,1088 @@
+use crate::{BTreeMap, BTreeSet, String, ToString, Vec};
+use crate::{BatchLCProof, Error, Evaluations, QuerySet};
+use crate::{LabeledCommitment, LabeledPolynomial, LinearCombination};
+use crate::{PCCommitterKey, PCRandomness, PCUniversalParams, Polynomial, PolynomialCommitment};
+
+use algebra_core::{
+    to_bytes, AffineCurve, Field, One, PrimeField, ProjectiveCurve, ToBytes, UniformRand,
+    VariableBaseMSM, Zero,
+};
+use core::{convert::TryInto, marker::PhantomData};
+use rand_core::RngCore;
+
+mod data_structures;
+pub use data_structures::*;
+
+#[cfg(feature = "parallel")]
+use rayon::prelude::*;
+
+use digest::Digest;
+
+/// A polynomial commitment scheme based on the hardness of the
+/// discrete logarithm problem in prime-order groups.
+/// The construction is described in detail in [[BCMS20]][pcdas].
+///
+/// Degree bound enforcement requires that (at least one of) the points at
+/// which a committed polynomial is evaluated are from a distribution that is
+/// random conditioned on the polynomial. This is because degree bound
+/// enforcement relies on checking a polynomial identity at this point.
+/// More formally, the points must be sampled from an admissible query sampler,
+/// as detailed in [[CHMMVW20]][marlin].
+///
+/// [pcdas]: https://eprint.iacr.org/2020/499
+/// [marlin]: https://eprint.iacr.org/2019/104
+pub struct InnerProductArgPC<G: AffineCurve, D: Digest> {
+    _projective: PhantomData<G>,
+    _digest: PhantomData<D>,
+}
+
+impl<G: AffineCurve, D: Digest> InnerProductArgPC<G, D> {
+    /// `PROTOCOL_NAME` is used as a seed for the setup function.
+    pub const PROTOCOL_NAME: &'static [u8] = b"PC-DL-2020";
+
+    /// 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
+    }
+
+    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()
+    }
+
+    #[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()
+    }
+
+    /// 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)
+    }
+
+    fn check_degrees_and_bounds(
+        supported_degree: usize,
+        p: &LabeledPolynomial<G::ScalarField>,
+    ) -> Result<(), Error> {
+        if p.degree() < 1 {
+            return Err(Error::DegreeIsZero);
+        } else if p.degree() > supported_degree {
+            return Err(Error::TooManyCoefficients {
+                num_coefficients: p.degree() + 1,
+                num_powers: supported_degree + 1,
+            });
+        }
+
+        if let Some(bound) = p.degree_bound() {
+            if bound < p.degree() || bound > supported_degree {
+                return Err(Error::IncorrectDegreeBound {
+                    poly_degree: p.degree(),
+                    degree_bound: bound,
+                    supported_degree,
+                    label: p.label().to_string(),
+                });
+            }
+        }
+
+        Ok(())
+    }
+
+    fn shift_polynomial(
+        ck: &CommitterKey<G>,
+        p: &Polynomial<G::ScalarField>,
+        degree_bound: usize,
+    ) -> Polynomial<G::ScalarField> {
+        if p.is_zero() {
+            Polynomial::zero()
+        } else {
+            let mut shifted_polynomial_coeffs =
+                vec![G::ScalarField::zero(); ck.supported_degree() - degree_bound];
+            shifted_polynomial_coeffs.extend_from_slice(&p.coeffs);
+            Polynomial::from_coefficients_vec(shifted_polynomial_coeffs)
+        }
+    }
+
+    fn combine_shifted_rand(
+        combined_rand: Option<G::ScalarField>,
+        new_rand: Option<G::ScalarField>,
+        coeff: G::ScalarField,
+    ) -> Option<G::ScalarField> {
+        if let Some(new_rand) = new_rand {
+            let coeff_new_rand = new_rand * &coeff;
+            return Some(combined_rand.map_or(coeff_new_rand, |r| r + &coeff_new_rand));
+        };
+
+        combined_rand
+    }
+
+    fn combine_shifted_comm(
+        combined_comm: Option<G::Projective>,
+        new_comm: Option<G>,
+        coeff: G::ScalarField,
+    ) -> Option<G::Projective> {
+        if let Some(new_comm) = new_comm {
+            let coeff_new_comm = new_comm.mul(coeff);
+            return Some(combined_comm.map_or(coeff_new_comm, |c| c + &coeff_new_comm));
+        };
+
+        combined_comm
+    }
+
+    fn construct_labeled_commitments(
+        lc_info: &[(String, Option<usize>)],
+        elements: &[G::Projective],
+    ) -> Vec<LabeledCommitment<Commitment<G>>> {
+        let comms = G::Projective::batch_normalization_into_affine(elements);
+        let mut commitments = Vec::new();
+
+        let mut i = 0;
+        for info in lc_info.into_iter() {
+            let commitment;
+            let label = info.0.clone();
+            let degree_bound = info.1;
+
+            if degree_bound.is_some() {
+                commitment = Commitment {
+                    comm: comms[i].clone(),
+                    shifted_comm: Some(comms[i + 1].clone()),
+                };
+
+                i += 2;
+            } else {
+                commitment = Commitment {
+                    comm: comms[i].clone(),
+                    shifted_comm: None,
+                };
+
+                i += 1;
+            }
+
+            commitments.push(LabeledCommitment::new(label, commitment, degree_bound));
+        }
+
+        return commitments;
+    }
+
+    fn sample_generators(num_generators: usize) -> Vec<G> {
+        let generators: Vec<_> = ff_fft::cfg_into_iter!(0..num_generators)
+            .map(|i| {
+                let i = i as u64;
+                let mut hash = D::digest(&to_bytes![&Self::PROTOCOL_NAME, i].unwrap());
+                let mut g = G::from_random_bytes(&hash);
+                let mut j = 0u64;
+                while g.is_none() {
+                    hash = D::digest(&to_bytes![&Self::PROTOCOL_NAME, i, j].unwrap());
+                    g = G::from_random_bytes(&hash);
+                    j += 1;
+                }
+                let generator = g.unwrap();
+                generator.mul_by_cofactor_to_projective()
+            })
+            .collect();
+
+        G::Projective::batch_normalization_into_affine(&generators)
+    }
+}
+
+impl<G: AffineCurve, D: Digest> PolynomialCommitment<G::ScalarField> for InnerProductArgPC<G, D> {
+    type UniversalParams = UniversalParams<G>;
+    type CommitterKey = CommitterKey<G>;
+    type VerifierKey = VerifierKey<G>;
+    type Commitment = Commitment<G>;
+    type Randomness = Randomness<G>;
+    type Proof = Proof<G>;
+    type BatchProof = Vec<Self::Proof>;
+    type Error = Error;
+
+    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)
+    }
+
+    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))
+    }
+
+    /// 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 hiding_bound.is_some() {
+                Randomness::rand(hiding_bound.unwrap(), 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))
+    }
+
+    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))
+        {
+            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;
+
+            assert_eq!(degree_bound.is_some(), randomness.shifted_rand.is_some());
+            assert_eq!(degree_bound.is_some(), commitment.shifted_comm.is_some());
+
+            if let Some(degree_bound) = labeled_polynomial.degree_bound() {
+                assert!(labeled_commitment.degree_bound().is_some());
+                assert_eq!(labeled_commitment.degree_bound().unwrap(), 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() {
+                    combined_rand += &(cur_challenge * &randomness.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,
+        })
+    }
+
+    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)
+    }
+
+    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)
+    }
+
+    fn open_combinations<'a>(
+        ck: &Self::CommitterKey,
+        lc_s: impl IntoIterator<Item = &'a LinearCombination<G::ScalarField>>,
+        polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<'a, G::ScalarField>>,
+        commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
+        query_set: &QuerySet<G::ScalarField>,
+        opening_challenge: G::ScalarField,
+        rands: impl IntoIterator<Item = &'a Self::Randomness>,
+        rng: Option<&mut dyn RngCore>,
+    ) -> Result<BatchLCProof<G::ScalarField, Self>, Self::Error>
+    where
+        Self::Randomness: 'a,
+        Self::Commitment: 'a,
+    {
+        let label_poly_map = polynomials
+            .into_iter()
+            .zip(rands)
+            .zip(commitments)
+            .map(|((p, r), c)| (p.label(), (p, r, c)))
+            .collect::<BTreeMap<_, _>>();
+
+        let mut lc_polynomials = Vec::new();
+        let mut lc_randomness = Vec::new();
+        let mut lc_commitments = Vec::new();
+        let mut lc_info = Vec::new();
+
+        for lc in lc_s {
+            let lc_label = lc.label().clone();
+            let mut poly = Polynomial::zero();
+            let mut degree_bound = None;
+            let mut hiding_bound = None;
+
+            let mut combined_comm = G::Projective::zero();
+            let mut combined_shifted_comm: Option<G::Projective> = None;
+
+            let mut combined_rand = G::ScalarField::zero();
+            let mut combined_shifted_rand: Option<G::ScalarField> = None;
+
+            let num_polys = lc.len();
+            for (coeff, label) in lc.iter().filter(|(_, l)| !l.is_one()) {
+                let label: &String = label.try_into().expect("cannot be one!");
+                let &(cur_poly, cur_rand, cur_comm) =
+                    label_poly_map.get(label).ok_or(Error::MissingPolynomial {
+                        label: label.to_string(),
+                    })?;
+
+                if num_polys == 1 && cur_poly.degree_bound().is_some() {
+                    assert!(
+                        coeff.is_one(),
+                        "Coefficient must be one for degree-bounded equations"
+                    );
+                    degree_bound = cur_poly.degree_bound();
+                } else if cur_poly.degree_bound().is_some() {
+                    eprintln!("Degree bound when number of equations is non-zero");
+                    return Err(Self::Error::EquationHasDegreeBounds(lc_label));
+                }
+
+                // Some(_) > None, always.
+                hiding_bound = core::cmp::max(hiding_bound, cur_poly.hiding_bound());
+                poly += (*coeff, cur_poly.polynomial());
+
+                combined_rand += &(cur_rand.rand * coeff);
+                combined_shifted_rand = Self::combine_shifted_rand(
+                    combined_shifted_rand,
+                    cur_rand.shifted_rand,
+                    *coeff,
+                );
+
+                let commitment = cur_comm.commitment();
+                combined_comm += &commitment.comm.mul(*coeff);
+                combined_shifted_comm = Self::combine_shifted_comm(
+                    combined_shifted_comm,
+                    commitment.shifted_comm,
+                    *coeff,
+                );
+            }
+
+            let lc_poly =
+                LabeledPolynomial::new_owned(lc_label.clone(), poly, degree_bound, hiding_bound);
+            lc_polynomials.push(lc_poly);
+            lc_randomness.push(Randomness {
+                rand: combined_rand,
+                shifted_rand: combined_shifted_rand,
+            });
+
+            lc_commitments.push(combined_comm);
+            if let Some(combined_shifted_comm) = combined_shifted_comm {
+                lc_commitments.push(combined_shifted_comm);
+            }
+
+            lc_info.push((lc_label, degree_bound));
+        }
+
+        let lc_commitments = Self::construct_labeled_commitments(&lc_info, &lc_commitments);
+
+        let proof = Self::batch_open(
+            ck,
+            lc_polynomials.iter(),
+            lc_commitments.iter(),
+            &query_set,
+            opening_challenge,
+            lc_randomness.iter(),
+            rng,
+        )?;
+        Ok(BatchLCProof { proof, evals: None })
+    }
+
+    /// Checks that `values` are the true evaluations at `query_set` of the polynomials
+    /// committed in `labeled_commitments`.
+    fn check_combinations<'a, R: RngCore>(
+        vk: &Self::VerifierKey,
+        lc_s: impl IntoIterator<Item = &'a LinearCombination<G::ScalarField>>,
+        commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
+        query_set: &QuerySet<G::ScalarField>,
+        evaluations: &Evaluations<G::ScalarField>,
+        proof: &BatchLCProof<G::ScalarField, Self>,
+        opening_challenge: G::ScalarField,
+        rng: &mut R,
+    ) -> Result<bool, Self::Error>
+    where
+        Self::Commitment: 'a,
+    {
+        let BatchLCProof { proof, .. } = proof;
+        let label_comm_map = commitments
+            .into_iter()
+            .map(|c| (c.label(), c))
+            .collect::<BTreeMap<_, _>>();
+
+        let mut lc_commitments = Vec::new();
+        let mut lc_info = Vec::new();
+        let mut evaluations = evaluations.clone();
+        for lc in lc_s {
+            let lc_label = lc.label().clone();
+            let num_polys = lc.len();
+
+            let mut degree_bound = None;
+            let mut combined_comm = G::Projective::zero();
+            let mut combined_shifted_comm: Option<G::Projective> = None;
+
+            for (coeff, label) in lc.iter() {
+                if label.is_one() {
+                    for (&(ref label, _), ref mut eval) in evaluations.iter_mut() {
+                        if label == &lc_label {
+                            **eval -= coeff;
+                        }
+                    }
+                } else {
+                    let label: &String = label.try_into().unwrap();
+                    let &cur_comm = label_comm_map.get(label).ok_or(Error::MissingPolynomial {
+                        label: label.to_string(),
+                    })?;
+
+                    if num_polys == 1 && cur_comm.degree_bound().is_some() {
+                        assert!(
+                            coeff.is_one(),
+                            "Coefficient must be one for degree-bounded equations"
+                        );
+                        degree_bound = cur_comm.degree_bound();
+                    } else if cur_comm.degree_bound().is_some() {
+                        return Err(Self::Error::EquationHasDegreeBounds(lc_label));
+                    }
+
+                    let commitment = cur_comm.commitment();
+                    combined_comm += &commitment.comm.mul(*coeff);
+                    combined_shifted_comm = Self::combine_shifted_comm(
+                        combined_shifted_comm,
+                        commitment.shifted_comm,
+                        *coeff,
+                    );
+                }
+            }
+
+            lc_commitments.push(combined_comm);
+
+            if let Some(combined_shifted_comm) = combined_shifted_comm {
+                lc_commitments.push(combined_shifted_comm);
+            }
+
+            lc_info.push((lc_label, degree_bound));
+        }
+
+        let lc_commitments = Self::construct_labeled_commitments(&lc_info, &lc_commitments);
+
+        Self::batch_check(
+            vk,
+            &lc_commitments,
+            &query_set,
+            &evaluations,
+            proof,
+            opening_challenge,
+            rng,
+        )
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    #![allow(non_camel_case_types)]
+
+    use super::InnerProductArgPC;
+
+    use algebra::jubjub::JubJubAffine;
+    use blake2::Blake2s;
+
+    type PC<E, D> = InnerProductArgPC<E, D>;
+    type PC_JJB2S = PC<JubJubAffine, Blake2s>;
+
+    #[test]
+    fn single_poly_test() {
+        use crate::tests::*;
+        single_poly_test::<_, PC_JJB2S>().expect("test failed for jubjub-blake2s");
+    }
+
+    #[test]
+    fn quadratic_poly_degree_bound_multiple_queries_test() {
+        use crate::tests::*;
+        quadratic_poly_degree_bound_multiple_queries_test::<_, PC_JJB2S>()
+            .expect("test failed for jubjub-blake2s");
+    }
+
+    #[test]
+    fn linear_poly_degree_bound_test() {
+        use crate::tests::*;
+        linear_poly_degree_bound_test::<_, PC_JJB2S>().expect("test failed for jubjub-blake2s");
+    }
+
+    #[test]
+    fn single_poly_degree_bound_test() {
+        use crate::tests::*;
+        single_poly_degree_bound_test::<_, PC_JJB2S>().expect("test failed for jubjub-blake2s");
+    }
+
+    #[test]
+    fn single_poly_degree_bound_multiple_queries_test() {
+        use crate::tests::*;
+        single_poly_degree_bound_multiple_queries_test::<_, PC_JJB2S>()
+            .expect("test failed for jubjub-blake2s");
+    }
+
+    #[test]
+    fn two_polys_degree_bound_single_query_test() {
+        use crate::tests::*;
+        two_polys_degree_bound_single_query_test::<_, PC_JJB2S>()
+            .expect("test failed for jubjub-blake2s");
+    }
+
+    #[test]
+    fn full_end_to_end_test() {
+        use crate::tests::*;
+        full_end_to_end_test::<_, PC_JJB2S>().expect("test failed for jubjub-blake2s");
+        println!("Finished jubjub-blake2s");
+    }
+
+    #[test]
+    fn single_equation_test() {
+        use crate::tests::*;
+        single_equation_test::<_, PC_JJB2S>().expect("test failed for jubjub-blake2s");
+        println!("Finished jubjub-blake2s");
+    }
+
+    #[test]
+    fn two_equation_test() {
+        use crate::tests::*;
+        two_equation_test::<_, PC_JJB2S>().expect("test failed for jubjub-blake2s");
+        println!("Finished jubjub-blake2s");
+    }
+
+    #[test]
+    fn two_equation_degree_bound_test() {
+        use crate::tests::*;
+        two_equation_degree_bound_test::<_, PC_JJB2S>().expect("test failed for jubjub-blake2s");
+        println!("Finished jubjub-blake2s");
+    }
+
+    #[test]
+    fn full_end_to_end_equation_test() {
+        use crate::tests::*;
+        full_end_to_end_equation_test::<_, PC_JJB2S>().expect("test failed for jubjub-blake2s");
+        println!("Finished jubjub-blake2s");
+    }
+
+    #[test]
+    #[should_panic]
+    fn bad_degree_bound_test() {
+        use crate::tests::*;
+        bad_degree_bound_test::<_, PC_JJB2S>().expect("test failed for jubjub-blake2s");
+        println!("Finished jubjub-blake2s");
+    }
+}
diff --git a/src/kzg10/data_structures.rs b/src/kzg10/data_structures.rs
index 02dca76..9f88c5f 100644
--- a/src/kzg10/data_structures.rs
+++ b/src/kzg10/data_structures.rs
@@ -233,6 +233,9 @@ impl<E: PairingEngine> ToBytes for Proof<E> {
     #[inline]
     fn write<W: algebra_core::io::Write>(&self, mut writer: W) -> algebra_core::io::Result<()> {
         self.w.write(&mut writer)?;
-        self.random_v.write(&mut writer)
+        self.random_v
+            .as_ref()
+            .unwrap_or(&E::Fr::zero())
+            .write(&mut writer)
     }
 }
diff --git a/src/kzg10/mod.rs b/src/kzg10/mod.rs
index edd2329..1fa2a8b 100644
--- a/src/kzg10/mod.rs
+++ b/src/kzg10/mod.rs
@@ -2,7 +2,7 @@
 //! single polynomial `p`, and then later provide an evaluation proof that
 //! convinces verifiers that a claimed value `v` is the true evaluation of `p`
 //! at a chosen point `x`. Our construction follows the template of the construction
-//! proposed by Kate, Zaverucha, and Goldberg ([KZG10](http://cacr.uwaterloo.ca/techreports/2010/cacr2010-10.pdf)).
+//! proposed by Kate, Zaverucha, and Goldberg ([KZG11](http://cacr.uwaterloo.ca/techreports/2010/cacr2010-10.pdf)).
 //! This construction achieves extractability in the algebraic group model (AGM).
 
 use crate::{Error, LabeledPolynomial, PCRandomness, Polynomial, ToString, Vec};
@@ -19,8 +19,6 @@ use core::marker::PhantomData;
 mod data_structures;
 pub use data_structures::*;
 
-pub(crate) mod optional_rng;
-
 /// `KZG10` is an implementation of the polynomial commitment scheme of
 /// [Kate, Zaverucha and Goldbgerg][kzg10]
 ///
@@ -465,7 +463,6 @@ mod tests {
     use algebra::test_rng;
     use algebra::Bls12_377;
     use algebra::Bls12_381;
-    use algebra::SW6;
 
     type KZG_Bls12_381 = KZG10<Bls12_381>;
 
@@ -612,19 +609,16 @@ mod tests {
     fn end_to_end_test() {
         end_to_end_test_template::<Bls12_377>().expect("test failed for bls12-377");
         end_to_end_test_template::<Bls12_381>().expect("test failed for bls12-381");
-        end_to_end_test_template::<SW6>().expect("test failed for SW6");
     }
 
     #[test]
     fn linear_polynomial_test() {
         linear_polynomial_test_template::<Bls12_377>().expect("test failed for bls12-377");
         linear_polynomial_test_template::<Bls12_381>().expect("test failed for bls12-381");
-        linear_polynomial_test_template::<SW6>().expect("test failed for SW6");
     }
     #[test]
     fn batch_check_test() {
         batch_check_test_template::<Bls12_377>().expect("test failed for bls12-377");
         batch_check_test_template::<Bls12_381>().expect("test failed for bls12-381");
-        batch_check_test_template::<SW6>().expect("test failed for SW6");
     }
 }
diff --git a/src/kzg10/optional_rng.rs b/src/kzg10/optional_rng.rs
deleted file mode 100644
index f31609a..0000000
--- a/src/kzg10/optional_rng.rs
+++ /dev/null
@@ -1,31 +0,0 @@
-use rand_core::RngCore;
-
-// This trick is necessary because `Option<&mut R>` is not implicitly reborrowed
-// like `&mut R` is. As a result, we define a dummy rng here that should be used
-// when `commit` gets `rng = None`
-//
-// Basically, we define a "dummy rng" that does nothing
-// (corresponding to the case that `rng = None`).
-pub(crate) struct OptionalRng<R>(pub(crate) Option<R>);
-
-impl<R: RngCore> RngCore for OptionalRng<R> {
-    #[inline]
-    fn next_u32(&mut self) -> u32 {
-        (&mut self.0).as_mut().map_or(0, |r| r.next_u32())
-    }
-
-    #[inline]
-    fn next_u64(&mut self) -> u64 {
-        (&mut self.0).as_mut().map_or(0, |r| r.next_u64())
-    }
-
-    #[inline]
-    fn fill_bytes(&mut self, dest: &mut [u8]) {
-        (&mut self.0).as_mut().map_or((), |r| r.fill_bytes(dest))
-    }
-
-    #[inline]
-    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
-        Ok(self.fill_bytes(dest))
-    }
-}
diff --git a/src/lib.rs b/src/lib.rs
index 8155d32..3fa9b38 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -3,7 +3,8 @@
 #![deny(unused_import_braces, unused_qualifications, trivial_casts)]
 #![deny(trivial_numeric_casts, private_in_public, variant_size_differences)]
 #![deny(stable_features, unreachable_pub, non_shorthand_field_patterns)]
-#![deny(unused_attributes, unused_mut, missing_docs)]
+#![deny(unused_attributes, unused_mut)]
+#![deny(missing_docs)]
 #![deny(unused_imports)]
 #![deny(renamed_and_removed_lints, stable_features, unused_allocation)]
 #![deny(unused_comparisons, bare_trait_objects, unused_must_use, const_err)]
@@ -47,6 +48,10 @@ pub use data_structures::*;
 pub mod error;
 pub use error::*;
 
+/// A random number generator that bypasses some limitations of the Rust borrow
+/// checker.
+pub mod optional_rng;
+
 #[cfg(not(feature = "std"))]
 macro_rules! eprintln {
     () => {};
@@ -63,7 +68,7 @@ pub mod kzg10;
 ///
 /// [kzg]: http://cacr.uwaterloo.ca/techreports/2010/cacr2010-10.pdf
 /// [marlin]: https://eprint.iacr.org/2019/1047
-pub mod marlin_kzg10;
+pub mod marlin_pc;
 
 /// Polynomial commitment scheme based on the construction in [[KZG10]][kzg],
 /// modified to obtain batching and to enforce strict
@@ -75,7 +80,14 @@ pub mod marlin_kzg10;
 /// [sonic]: https://eprint.iacr.org/2019/099
 /// [al]: https://eprint.iacr.org/2019/601
 /// [marlin]: https://eprint.iacr.org/2019/1047
-pub mod sonic_kzg10;
+pub mod sonic_pc;
+
+/// A polynomial commitment scheme based on the hardness of the
+/// discrete logarithm problem in prime-order groups.
+/// The construction is detailed in [[BCMS20]][pcdas].
+///
+/// [pcdas]: https://eprint.iacr.org/2020/499
+pub mod ipa_pc;
 
 /// `QuerySet` is the set of queries that are to be made to a set of labeled polynomials/equations
 /// `p` that have previously been committed to. Each element of a `QuerySet` is a `(label, query)`
@@ -162,34 +174,42 @@ pub trait PolynomialCommitment<F: Field>: Sized {
     fn open<'a>(
         ck: &Self::CommitterKey,
         labeled_polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<'a, F>>,
+        commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
         point: F,
         opening_challenge: F,
         rands: impl IntoIterator<Item = &'a Self::Randomness>,
+        rng: Option<&mut dyn RngCore>,
     ) -> Result<Self::Proof, Self::Error>
     where
-        Self::Randomness: 'a;
+        Self::Randomness: 'a,
+        Self::Commitment: 'a;
 
     /// On input a list of labeled polynomials and a query set, `open` outputs a proof of evaluation
     /// of the polynomials at the points in the query set.
     fn batch_open<'a>(
         ck: &Self::CommitterKey,
         labeled_polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<'a, F>>,
+        commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
         query_set: &QuerySet<F>,
         opening_challenge: F,
         rands: impl IntoIterator<Item = &'a Self::Randomness>,
+        rng: Option<&mut dyn RngCore>,
     ) -> Result<Self::BatchProof, Self::Error>
     where
         Self::Randomness: 'a,
+        Self::Commitment: 'a,
     {
-        let polynomials_with_rands: BTreeMap<_, _> = labeled_polynomials
+        let rng = &mut crate::optional_rng::OptionalRng(rng);
+        let poly_rand_comm: BTreeMap<_, _> = labeled_polynomials
             .into_iter()
             .zip(rands)
-            .map(|(poly, r)| (poly.label(), (poly, r)))
+            .zip(commitments.into_iter())
+            .map(|((poly, r), comm)| (poly.label(), (poly, r, comm)))
             .collect();
 
         let open_time = start_timer!(|| format!(
             "Opening {} polynomials at query set of size {}",
-            polynomials_with_rands.len(),
+            poly_rand_comm.len(),
             query_set.len(),
         ));
 
@@ -204,18 +224,30 @@ pub trait PolynomialCommitment<F: Field>: Sized {
         for (query, labels) in query_to_labels_map.into_iter() {
             let mut query_polys: Vec<&'a LabeledPolynomial<'a, _>> = Vec::new();
             let mut query_rands: Vec<&'a Self::Randomness> = Vec::new();
+            let mut query_comms: Vec<&'a LabeledCommitment<Self::Commitment>> = Vec::new();
+
             for label in labels {
-                let (polynomial, rand) =
-                    polynomials_with_rands
-                        .get(label)
-                        .ok_or(Error::MissingPolynomial {
-                            label: label.to_string(),
-                        })?;
+                let (polynomial, rand, comm) =
+                    poly_rand_comm.get(label).ok_or(Error::MissingPolynomial {
+                        label: label.to_string(),
+                    })?;
+
                 query_polys.push(polynomial);
                 query_rands.push(rand);
+                query_comms.push(comm);
             }
+
             let proof_time = start_timer!(|| "Creating proof");
-            let proof = Self::open(ck, query_polys, *query, opening_challenge, query_rands)?;
+            let proof = Self::open(
+                ck,
+                query_polys,
+                query_comms,
+                *query,
+                opening_challenge,
+                query_rands,
+                Some(rng),
+            )?;
+
             end_timer!(proof_time);
 
             proofs.push(proof);
@@ -227,13 +259,14 @@ pub trait PolynomialCommitment<F: Field>: Sized {
 
     /// Verifies that `values` are the evaluations at `point` of the polynomials
     /// committed inside `commitments`.
-    fn check<'a>(
+    fn check<'a, R: RngCore>(
         vk: &Self::VerifierKey,
         commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
         point: F,
         values: impl IntoIterator<Item = F>,
         proof: &Self::Proof,
         opening_challenge: F,
+        rng: &mut R,
     ) -> Result<bool, Self::Error>
     where
         Self::Commitment: 'a;
@@ -247,7 +280,7 @@ pub trait PolynomialCommitment<F: Field>: Sized {
         evaluations: &Evaluations<F>,
         proof: &Self::BatchProof,
         opening_challenge: F,
-        _rng: &mut R,
+        rng: &mut R,
     ) -> Result<bool, Self::Error>
     where
         Self::Commitment: 'a,
@@ -285,7 +318,7 @@ pub trait PolynomialCommitment<F: Field>: Sized {
             }
 
             let proof_time = start_timer!(|| "Checking per-query proof");
-            result &= Self::check(vk, comms, *query, values, &proof, opening_challenge)?;
+            result &= Self::check(vk, comms, *query, values, &proof, opening_challenge, rng)?;
             end_timer!(proof_time);
         }
         Ok(result)
@@ -298,19 +331,30 @@ pub trait PolynomialCommitment<F: Field>: Sized {
         ck: &Self::CommitterKey,
         linear_combinations: impl IntoIterator<Item = &'a LinearCombination<F>>,
         polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<'a, F>>,
+        commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
         query_set: &QuerySet<F>,
         opening_challenge: F,
         rands: impl IntoIterator<Item = &'a Self::Randomness>,
+        rng: Option<&mut dyn RngCore>,
     ) -> Result<BatchLCProof<F, Self>, Self::Error>
     where
         Self::Randomness: 'a,
+        Self::Commitment: 'a,
     {
         let linear_combinations: Vec<_> = linear_combinations.into_iter().collect();
         let polynomials: Vec<_> = polynomials.into_iter().collect();
         let poly_query_set =
             lc_query_set_to_poly_query_set(linear_combinations.iter().copied(), query_set);
         let poly_evals = evaluate_query_set(polynomials.iter().copied(), &poly_query_set);
-        let proof = Self::batch_open(ck, polynomials, &poly_query_set, opening_challenge, rands)?;
+        let proof = Self::batch_open(
+            ck,
+            polynomials,
+            commitments,
+            &poly_query_set,
+            opening_challenge,
+            rands,
+            rng,
+        )?;
         Ok(BatchLCProof {
             proof,
             evals: Some(poly_evals.values().copied().collect()),
@@ -438,6 +482,83 @@ pub mod tests {
         num_equations: Option<usize>,
     }
 
+    pub fn bad_degree_bound_test<F, PC>() -> Result<(), PC::Error>
+    where
+        F: Field,
+        PC: PolynomialCommitment<F>,
+    {
+        let rng = &mut test_rng();
+        let max_degree = 100;
+        let pp = PC::setup(max_degree, rng)?;
+
+        for _ in 0..10 {
+            let supported_degree = rand::distributions::Uniform::from(1..=max_degree).sample(rng);
+            assert!(
+                max_degree >= supported_degree,
+                "max_degree < supported_degree"
+            );
+
+            let mut labels = Vec::new();
+            let mut polynomials = Vec::new();
+            let mut degree_bounds = Vec::new();
+
+            for i in 0..10 {
+                let label = format!("Test{}", i);
+                labels.push(label.clone());
+                let poly = Polynomial::rand(supported_degree, rng);
+
+                let degree_bound = 1usize;
+                let hiding_bound = Some(1);
+                degree_bounds.push(degree_bound);
+
+                polynomials.push(LabeledPolynomial::new_owned(
+                    label,
+                    poly,
+                    Some(degree_bound),
+                    hiding_bound,
+                ))
+            }
+
+            println!("supported degree: {:?}", supported_degree);
+            let (ck, vk) = PC::trim(&pp, supported_degree, Some(degree_bounds.as_slice()))?;
+            println!("Trimmed");
+
+            let (comms, rands) = PC::commit(&ck, &polynomials, Some(rng))?;
+
+            let mut query_set = QuerySet::new();
+            let mut values = Evaluations::new();
+            let point = F::rand(rng);
+            for (i, label) in labels.iter().enumerate() {
+                query_set.insert((label.clone(), point));
+                let value = polynomials[i].evaluate(point);
+                values.insert((label.clone(), point), value);
+            }
+            println!("Generated query set");
+
+            let opening_challenge = F::rand(rng);
+            let proof = PC::batch_open(
+                &ck,
+                &polynomials,
+                &comms,
+                &query_set,
+                opening_challenge,
+                &rands,
+                Some(rng),
+            )?;
+            let result = PC::batch_check(
+                &vk,
+                &comms,
+                &query_set,
+                &values,
+                &proof,
+                opening_challenge,
+                rng,
+            )?;
+            assert!(result, "proof was incorrect, Query set: {:#?}", query_set);
+        }
+        Ok(())
+    }
+
     fn test_template<F, PC>(info: TestInfo) -> Result<(), PC::Error>
     where
         F: Field,
@@ -533,7 +654,15 @@ pub mod tests {
             println!("Generated query set");
 
             let opening_challenge = F::rand(rng);
-            let proof = PC::batch_open(&ck, &polynomials, &query_set, opening_challenge, &rands)?;
+            let proof = PC::batch_open(
+                &ck,
+                &polynomials,
+                &comms,
+                &query_set,
+                opening_challenge,
+                &rands,
+                Some(rng),
+            )?;
             let result = PC::batch_check(
                 &vk,
                 &comms,
@@ -694,9 +823,11 @@ pub mod tests {
                 &ck,
                 &linear_combinations,
                 &polynomials,
+                &comms,
                 &query_set,
                 opening_challenge,
                 &rands,
+                Some(rng),
             )?;
             println!("Generated proof");
             let result = PC::check_combinations(
diff --git a/src/marlin_kzg10/data_structures.rs b/src/marlin_pc/data_structures.rs
similarity index 98%
rename from src/marlin_kzg10/data_structures.rs
rename to src/marlin_pc/data_structures.rs
index 91d9dc4..12bd6a9 100644
--- a/src/marlin_kzg10/data_structures.rs
+++ b/src/marlin_pc/data_structures.rs
@@ -7,7 +7,7 @@ use crate::kzg10;
 /// `UniversalParams` are the universal parameters for the KZG10 scheme.
 pub type UniversalParams<E> = kzg10::UniversalParams<E>;
 
-/// `ComitterKey` is used to commit to and create evaluation proofs for a given
+/// `CommitterKey` is used to commit to and create evaluation proofs for a given
 /// polynomial.
 #[derive(Derivative)]
 #[derivative(
diff --git a/src/marlin_kzg10/mod.rs b/src/marlin_pc/mod.rs
similarity index 92%
rename from src/marlin_kzg10/mod.rs
rename to src/marlin_pc/mod.rs
index 0f94fc3..c24332b 100644
--- a/src/marlin_kzg10/mod.rs
+++ b/src/marlin_pc/mod.rs
@@ -14,6 +14,13 @@ pub use data_structures::*;
 /// Polynomial commitment based on [[KZG10]][kzg], with degree enforcement, batching,
 /// and (optional) hiding property taken from [[CHMMVW20, “Marlin”]][marlin].
 ///
+/// Degree bound enforcement requires that (at least one of) the points at
+/// which a committed polynomial is evaluated are from a distribution that is
+/// random conditioned on the polynomial. This is because degree bound
+/// enforcement relies on checking a polynomial identity at this point.
+/// More formally, the points must be sampled from an admissible query sampler,
+/// as detailed in [[CHMMVW20]][marlin].
+///
 /// [kzg]: http://cacr.uwaterloo.ca/techreports/2010/cacr2010-10.pdf
 /// [marlin]: https://eprint.iacr.org/2019/104
 pub struct MarlinKZG10<E: PairingEngine> {
@@ -260,12 +267,12 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for MarlinKZG10<E> {
         ),
         Self::Error,
     > {
+        let rng = &mut crate::optional_rng::OptionalRng(rng);
         let commit_time = start_timer!(|| "Committing to polynomials");
 
         let mut commitments = Vec::new();
         let mut randomness = Vec::new();
 
-        let rng = &mut kzg10::optional_rng::OptionalRng(rng);
         for p in polynomials {
             let label = p.label();
             let degree_bound = p.degree_bound();
@@ -322,12 +329,15 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for MarlinKZG10<E> {
     fn open<'a>(
         ck: &Self::CommitterKey,
         labeled_polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<'a, E::Fr>>,
+        _commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
         point: E::Fr,
         opening_challenge: E::Fr,
         rands: impl IntoIterator<Item = &'a Self::Randomness>,
+        _rng: Option<&mut dyn RngCore>,
     ) -> Result<Self::Proof, Self::Error>
     where
         Self::Randomness: 'a,
+        Self::Commitment: 'a,
     {
         let mut p = Polynomial::zero();
         let mut r = kzg10::Randomness::empty();
@@ -408,13 +418,14 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for MarlinKZG10<E> {
 
     /// Verifies that `value` is the evaluation at `x` of the polynomial
     /// committed inside `comm`.
-    fn check<'a>(
+    fn check<'a, R: RngCore>(
         vk: &Self::VerifierKey,
         commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
         point: E::Fr,
         values: impl IntoIterator<Item = E::Fr>,
         proof: &Self::Proof,
         opening_challenge: E::Fr,
+        _rng: &mut R,
     ) -> Result<bool, Self::Error>
     where
         Self::Commitment: 'a,
@@ -511,38 +522,46 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for MarlinKZG10<E> {
         ck: &Self::CommitterKey,
         lc_s: impl IntoIterator<Item = &'a LinearCombination<E::Fr>>,
         polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<'a, E::Fr>>,
+        commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
         query_set: &QuerySet<E::Fr>,
         opening_challenge: E::Fr,
         rands: impl IntoIterator<Item = &'a Self::Randomness>,
+        rng: Option<&mut dyn RngCore>,
     ) -> Result<BatchLCProof<E::Fr, Self>, Self::Error>
     where
         Self::Randomness: 'a,
+        Self::Commitment: 'a,
     {
-        let label_poly_rand_map = polynomials
+        let label_map = polynomials
             .into_iter()
             .zip(rands)
-            .map(|(p, r)| (p.label(), (p, r)))
+            .zip(commitments)
+            .map(|((p, r), c)| (p.label(), (p, r, c)))
             .collect::<BTreeMap<_, _>>();
 
         let mut lc_polynomials = Vec::new();
         let mut lc_randomness = Vec::new();
+        let mut lc_commitments = Vec::new();
+        let mut lc_info = Vec::new();
+
         for lc in lc_s {
             let lc_label = lc.label().clone();
             let mut poly = Polynomial::zero();
             let mut degree_bound = None;
             let mut hiding_bound = None;
+
             let mut randomness = Self::Randomness::empty();
             assert!(randomness.shifted_rand.is_none());
 
+            let mut coeffs_and_comms = Vec::new();
+
             let num_polys = lc.len();
             for (coeff, label) in lc.iter().filter(|(_, l)| !l.is_one()) {
                 let label: &String = label.try_into().expect("cannot be one!");
-                let &(cur_poly, cur_rand) =
-                    label_poly_rand_map
-                        .get(label)
-                        .ok_or(Error::MissingPolynomial {
-                            label: label.to_string(),
-                        })?;
+                let &(cur_poly, cur_rand, cur_comm) =
+                    label_map.get(label).ok_or(Error::MissingPolynomial {
+                        label: label.to_string(),
+                    })?;
 
                 if num_polys == 1 && cur_poly.degree_bound().is_some() {
                     assert!(
@@ -559,23 +578,38 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for MarlinKZG10<E> {
                 hiding_bound = core::cmp::max(hiding_bound, cur_poly.hiding_bound());
                 poly += (*coeff, cur_poly.polynomial());
                 randomness += (*coeff, cur_rand);
+                coeffs_and_comms.push((*coeff, cur_comm.commitment()));
 
                 if degree_bound.is_none() {
                     assert!(randomness.shifted_rand.is_none());
                 }
             }
-            let lc_poly = LabeledPolynomial::new_owned(lc_label, poly, degree_bound, hiding_bound);
+
+            let lc_poly =
+                LabeledPolynomial::new_owned(lc_label.clone(), poly, degree_bound, hiding_bound);
             lc_polynomials.push(lc_poly);
             lc_randomness.push(randomness);
+            lc_commitments.push(Self::combine_commitments(coeffs_and_comms));
+            lc_info.push((lc_label, degree_bound));
         }
 
+        let comms = Self::normalize_commitments(lc_commitments);
+        let lc_commitments = lc_info
+            .into_iter()
+            .zip(comms)
+            .map(|((label, d), c)| LabeledCommitment::new(label, c, d))
+            .collect::<Vec<_>>();
+
         let proof = Self::batch_open(
             ck,
             lc_polynomials.iter(),
+            lc_commitments.iter(),
             &query_set,
             opening_challenge,
             lc_randomness.iter(),
+            rng,
         )?;
+
         Ok(BatchLCProof { proof, evals: None })
     }
 
@@ -669,22 +703,19 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for MarlinKZG10<E> {
 mod tests {
     #![allow(non_camel_case_types)]
 
-    use crate::marlin_kzg10::MarlinKZG10;
+    use super::MarlinKZG10;
     use algebra::Bls12_377;
     use algebra::Bls12_381;
-    use algebra::SW6;
 
     type PC<E> = MarlinKZG10<E>;
     type PC_Bls12_381 = PC<Bls12_381>;
     type PC_Bls12_377 = PC<Bls12_377>;
-    type PC_SW6 = PC<SW6>;
 
     #[test]
     fn single_poly_test() {
         use crate::tests::*;
         single_poly_test::<_, PC_Bls12_377>().expect("test failed for bls12-377");
         single_poly_test::<_, PC_Bls12_381>().expect("test failed for bls12-381");
-        single_poly_test::<_, PC_SW6>().expect("test failed for SW6");
     }
 
     #[test]
@@ -694,8 +725,6 @@ mod tests {
             .expect("test failed for bls12-377");
         quadratic_poly_degree_bound_multiple_queries_test::<_, PC_Bls12_381>()
             .expect("test failed for bls12-381");
-        quadratic_poly_degree_bound_multiple_queries_test::<_, PC_SW6>()
-            .expect("test failed for SW6");
     }
 
     #[test]
@@ -703,7 +732,6 @@ mod tests {
         use crate::tests::*;
         linear_poly_degree_bound_test::<_, PC_Bls12_377>().expect("test failed for bls12-377");
         linear_poly_degree_bound_test::<_, PC_Bls12_381>().expect("test failed for bls12-381");
-        linear_poly_degree_bound_test::<_, PC_SW6>().expect("test failed for SW6");
     }
 
     #[test]
@@ -711,7 +739,6 @@ mod tests {
         use crate::tests::*;
         single_poly_degree_bound_test::<_, PC_Bls12_377>().expect("test failed for bls12-377");
         single_poly_degree_bound_test::<_, PC_Bls12_381>().expect("test failed for bls12-381");
-        single_poly_degree_bound_test::<_, PC_SW6>().expect("test failed for SW6");
     }
 
     #[test]
@@ -721,7 +748,6 @@ mod tests {
             .expect("test failed for bls12-377");
         single_poly_degree_bound_multiple_queries_test::<_, PC_Bls12_381>()
             .expect("test failed for bls12-381");
-        single_poly_degree_bound_multiple_queries_test::<_, PC_SW6>().expect("test failed for SW6");
     }
 
     #[test]
@@ -731,7 +757,6 @@ mod tests {
             .expect("test failed for bls12-377");
         two_polys_degree_bound_single_query_test::<_, PC_Bls12_381>()
             .expect("test failed for bls12-381");
-        two_polys_degree_bound_single_query_test::<_, PC_SW6>().expect("test failed for SW6");
     }
 
     #[test]
@@ -741,8 +766,6 @@ mod tests {
         println!("Finished bls12-377");
         full_end_to_end_test::<_, PC_Bls12_381>().expect("test failed for bls12-381");
         println!("Finished bls12-381");
-        full_end_to_end_test::<_, PC_SW6>().expect("test failed for SW6");
-        println!("Finished sw6");
     }
 
     #[test]
@@ -752,8 +775,6 @@ mod tests {
         println!("Finished bls12-377");
         single_equation_test::<_, PC_Bls12_381>().expect("test failed for bls12-381");
         println!("Finished bls12-381");
-        single_equation_test::<_, PC_SW6>().expect("test failed for SW6");
-        println!("Finished sw6");
     }
 
     #[test]
@@ -763,8 +784,6 @@ mod tests {
         println!("Finished bls12-377");
         two_equation_test::<_, PC_Bls12_381>().expect("test failed for bls12-381");
         println!("Finished bls12-381");
-        two_equation_test::<_, PC_SW6>().expect("test failed for SW6");
-        println!("Finished sw6");
     }
 
     #[test]
@@ -774,8 +793,6 @@ mod tests {
         println!("Finished bls12-377");
         two_equation_degree_bound_test::<_, PC_Bls12_381>().expect("test failed for bls12-381");
         println!("Finished bls12-381");
-        two_equation_degree_bound_test::<_, PC_SW6>().expect("test failed for SW6");
-        println!("Finished sw6");
     }
 
     #[test]
@@ -785,7 +802,15 @@ mod tests {
         println!("Finished bls12-377");
         full_end_to_end_equation_test::<_, PC_Bls12_381>().expect("test failed for bls12-381");
         println!("Finished bls12-381");
-        full_end_to_end_equation_test::<_, PC_SW6>().expect("test failed for SW6");
-        println!("Finished sw6");
+    }
+
+    #[test]
+    #[should_panic]
+    fn bad_degree_bound_test() {
+        use crate::tests::*;
+        bad_degree_bound_test::<_, PC_Bls12_377>().expect("test failed for bls12-377");
+        println!("Finished bls12-377");
+        bad_degree_bound_test::<_, PC_Bls12_381>().expect("test failed for bls12-381");
+        println!("Finished bls12-381");
     }
 }
diff --git a/src/optional_rng.rs b/src/optional_rng.rs
new file mode 100644
index 0000000..62f8ecf
--- /dev/null
+++ b/src/optional_rng.rs
@@ -0,0 +1,52 @@
+use core::num::NonZeroU32;
+use rand_core::RngCore;
+
+/// `OptionalRng` is a hack that is necessary because `Option<&mut R>` is not implicitly reborrowed
+/// like `&mut R` is. This causes problems when a variable of type `Option<&mut R>`
+/// is moved (eg, in a loop).
+///
+/// To overcome this, we define the wrapper `OptionalRng` here that can be borrowed
+/// mutably, without fear of being moved.
+pub struct OptionalRng<R>(pub Option<R>);
+
+impl<R: RngCore> RngCore for OptionalRng<R> {
+    #[inline]
+    fn next_u32(&mut self) -> u32 {
+        (&mut self.0)
+            .as_mut()
+            .map(|r| r.next_u32())
+            .expect("Rng was invoked in a non-hiding context")
+    }
+
+    #[inline]
+    fn next_u64(&mut self) -> u64 {
+        (&mut self.0)
+            .as_mut()
+            .map(|r| r.next_u64())
+            .expect("Rng was invoked in a non-hiding context")
+    }
+
+    #[inline]
+    fn fill_bytes(&mut self, dest: &mut [u8]) {
+        (&mut self.0)
+            .as_mut()
+            .map(|r| r.fill_bytes(dest))
+            .expect("Rng was invoked in a non-hiding context")
+    }
+
+    #[inline]
+    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
+        match &mut self.0 {
+            Some(r) => r.try_fill_bytes(dest),
+            None => Err(NonZeroU32::new(rand_core::Error::CUSTOM_START)
+                .unwrap()
+                .into()),
+        }
+    }
+}
+
+impl<R: RngCore> From<R> for OptionalRng<R> {
+    fn from(other: R) -> Self {
+        Self(Some(other))
+    }
+}
diff --git a/src/sonic_kzg10/data_structures.rs b/src/sonic_pc/data_structures.rs
similarity index 100%
rename from src/sonic_kzg10/data_structures.rs
rename to src/sonic_pc/data_structures.rs
diff --git a/src/sonic_kzg10/mod.rs b/src/sonic_pc/mod.rs
similarity index 92%
rename from src/sonic_kzg10/mod.rs
rename to src/sonic_pc/mod.rs
index 1ccc809..0ef9f2a 100644
--- a/src/sonic_kzg10/mod.rs
+++ b/src/sonic_pc/mod.rs
@@ -259,10 +259,10 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for SonicKZG10<E> {
         ),
         Self::Error,
     > {
+        let rng = &mut crate::optional_rng::OptionalRng(rng);
         let commit_time = start_timer!(|| "Committing to polynomials");
         let mut labeled_comms: Vec<LabeledCommitment<Self::Commitment>> = Vec::new();
         let mut randomness: Vec<Self::Randomness> = Vec::new();
-        let rng = &mut kzg10::optional_rng::OptionalRng(rng);
 
         for labeled_polynomial in polynomials {
             let enforced_degree_bounds: Option<&[usize]> = ck
@@ -314,12 +314,15 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for SonicKZG10<E> {
     fn open<'a>(
         ck: &Self::CommitterKey,
         labeled_polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<'a, E::Fr>>,
+        _commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
         point: E::Fr,
         opening_challenge: E::Fr,
         rands: impl IntoIterator<Item = &'a Self::Randomness>,
+        _rng: Option<&mut dyn RngCore>,
     ) -> Result<Self::Proof, Self::Error>
     where
         Self::Randomness: 'a,
+        Self::Commitment: 'a,
     {
         let mut combined_polynomial = Polynomial::zero();
         let mut combined_rand = kzg10::Randomness::empty();
@@ -350,13 +353,14 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for SonicKZG10<E> {
         Ok(proof)
     }
 
-    fn check<'a>(
+    fn check<'a, R: RngCore>(
         vk: &Self::VerifierKey,
         commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
         point: E::Fr,
         values: impl IntoIterator<Item = E::Fr>,
         proof: &Self::Proof,
         opening_challenge: E::Fr,
+        _rng: &mut R,
     ) -> Result<bool, Self::Error>
     where
         Self::Commitment: 'a,
@@ -463,37 +467,43 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for SonicKZG10<E> {
         ck: &Self::CommitterKey,
         lc_s: impl IntoIterator<Item = &'a LinearCombination<E::Fr>>,
         polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<'a, E::Fr>>,
+        commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
         query_set: &QuerySet<E::Fr>,
         opening_challenge: E::Fr,
         rands: impl IntoIterator<Item = &'a Self::Randomness>,
+        rng: Option<&mut dyn RngCore>,
     ) -> Result<BatchLCProof<E::Fr, Self>, Self::Error>
     where
         Self::Randomness: 'a,
+        Self::Commitment: 'a,
     {
-        let label_poly_rand_map = polynomials
+        let label_map = polynomials
             .into_iter()
             .zip(rands)
-            .map(|(p, r)| (p.label(), (p, r)))
+            .zip(commitments)
+            .map(|((p, r), c)| (p.label(), (p, r, c)))
             .collect::<BTreeMap<_, _>>();
 
         let mut lc_polynomials = Vec::new();
         let mut lc_randomness = Vec::new();
+        let mut lc_commitments = Vec::new();
+        let mut lc_info = Vec::new();
+
         for lc in lc_s {
             let lc_label = lc.label().clone();
             let mut poly = Polynomial::zero();
             let mut degree_bound = None;
             let mut hiding_bound = None;
             let mut randomness = Self::Randomness::empty();
+            let mut comm = E::G1Projective::zero();
 
             let num_polys = lc.len();
             for (coeff, label) in lc.iter().filter(|(_, l)| !l.is_one()) {
                 let label: &String = label.try_into().expect("cannot be one!");
-                let &(cur_poly, cur_rand) =
-                    label_poly_rand_map
-                        .get(label)
-                        .ok_or(Error::MissingPolynomial {
-                            label: label.to_string(),
-                        })?;
+                let &(cur_poly, cur_rand, curr_comm) =
+                    label_map.get(label).ok_or(Error::MissingPolynomial {
+                        label: label.to_string(),
+                    })?;
 
                 if num_polys == 1 && cur_poly.degree_bound().is_some() {
                     assert!(
@@ -510,18 +520,37 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for SonicKZG10<E> {
                 hiding_bound = core::cmp::max(hiding_bound, cur_poly.hiding_bound());
                 poly += (*coeff, cur_poly.polynomial());
                 randomness += (*coeff, cur_rand);
+                comm += &curr_comm.commitment().0.into_projective().mul(*coeff);
             }
-            let lc_poly = LabeledPolynomial::new_owned(lc_label, poly, degree_bound, hiding_bound);
+
+            let lc_poly =
+                LabeledPolynomial::new_owned(lc_label.clone(), poly, degree_bound, hiding_bound);
             lc_polynomials.push(lc_poly);
             lc_randomness.push(randomness);
+            lc_commitments.push(comm);
+            lc_info.push((lc_label, degree_bound));
         }
 
+        let comms: Vec<Self::Commitment> =
+            E::G1Projective::batch_normalization_into_affine(&lc_commitments)
+                .into_iter()
+                .map(|c| kzg10::Commitment::<E>(c))
+                .collect();
+
+        let lc_commitments = lc_info
+            .into_iter()
+            .zip(comms)
+            .map(|((label, d), c)| LabeledCommitment::new(label, c, d))
+            .collect::<Vec<_>>();
+
         let proof = Self::batch_open(
             ck,
             lc_polynomials.iter(),
+            lc_commitments.iter(),
             &query_set,
             opening_challenge,
             lc_randomness.iter(),
+            rng,
         )?;
         Ok(BatchLCProof { proof, evals: None })
     }
@@ -615,22 +644,19 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for SonicKZG10<E> {
 mod tests {
     #![allow(non_camel_case_types)]
 
-    use crate::sonic_kzg10::SonicKZG10;
+    use super::SonicKZG10;
     use algebra::Bls12_377;
     use algebra::Bls12_381;
-    use algebra::SW6;
 
     type PC<E> = SonicKZG10<E>;
     type PC_Bls12_377 = PC<Bls12_377>;
     type PC_Bls12_381 = PC<Bls12_381>;
-    type PC_SW6 = PC<SW6>;
 
     #[test]
     fn single_poly_test() {
         use crate::tests::*;
         single_poly_test::<_, PC_Bls12_377>().expect("test failed for bls12-377");
         single_poly_test::<_, PC_Bls12_381>().expect("test failed for bls12-381");
-        single_poly_test::<_, PC_SW6>().expect("test failed for SW6");
     }
 
     #[test]
@@ -640,8 +666,6 @@ mod tests {
             .expect("test failed for bls12-377");
         quadratic_poly_degree_bound_multiple_queries_test::<_, PC_Bls12_381>()
             .expect("test failed for bls12-381");
-        quadratic_poly_degree_bound_multiple_queries_test::<_, PC_SW6>()
-            .expect("test failed for SW6");
     }
 
     #[test]
@@ -649,7 +673,6 @@ mod tests {
         use crate::tests::*;
         linear_poly_degree_bound_test::<_, PC_Bls12_377>().expect("test failed for bls12-377");
         linear_poly_degree_bound_test::<_, PC_Bls12_381>().expect("test failed for bls12-381");
-        linear_poly_degree_bound_test::<_, PC_SW6>().expect("test failed for SW6");
     }
 
     #[test]
@@ -657,7 +680,6 @@ mod tests {
         use crate::tests::*;
         single_poly_degree_bound_test::<_, PC_Bls12_377>().expect("test failed for bls12-377");
         single_poly_degree_bound_test::<_, PC_Bls12_381>().expect("test failed for bls12-381");
-        single_poly_degree_bound_test::<_, PC_SW6>().expect("test failed for SW6");
     }
 
     #[test]
@@ -667,7 +689,6 @@ mod tests {
             .expect("test failed for bls12-377");
         single_poly_degree_bound_multiple_queries_test::<_, PC_Bls12_381>()
             .expect("test failed for bls12-381");
-        single_poly_degree_bound_multiple_queries_test::<_, PC_SW6>().expect("test failed for SW6");
     }
 
     #[test]
@@ -677,7 +698,6 @@ mod tests {
             .expect("test failed for bls12-377");
         two_polys_degree_bound_single_query_test::<_, PC_Bls12_381>()
             .expect("test failed for bls12-381");
-        two_polys_degree_bound_single_query_test::<_, PC_SW6>().expect("test failed for SW6");
     }
 
     #[test]
@@ -687,8 +707,6 @@ mod tests {
         println!("Finished bls12-377");
         full_end_to_end_test::<_, PC_Bls12_381>().expect("test failed for bls12-381");
         println!("Finished bls12-381");
-        full_end_to_end_test::<_, PC_SW6>().expect("test failed for SW6");
-        println!("Finished sw6");
     }
 
     #[test]
@@ -698,8 +716,6 @@ mod tests {
         println!("Finished bls12-377");
         single_equation_test::<_, PC_Bls12_381>().expect("test failed for bls12-381");
         println!("Finished bls12-381");
-        single_equation_test::<_, PC_SW6>().expect("test failed for SW6");
-        println!("Finished sw6");
     }
 
     #[test]
@@ -709,8 +725,6 @@ mod tests {
         println!("Finished bls12-377");
         two_equation_test::<_, PC_Bls12_381>().expect("test failed for bls12-381");
         println!("Finished bls12-381");
-        two_equation_test::<_, PC_SW6>().expect("test failed for SW6");
-        println!("Finished sw6");
     }
 
     #[test]
@@ -720,8 +734,6 @@ mod tests {
         println!("Finished bls12-377");
         two_equation_degree_bound_test::<_, PC_Bls12_381>().expect("test failed for bls12-381");
         println!("Finished bls12-381");
-        two_equation_degree_bound_test::<_, PC_SW6>().expect("test failed for SW6");
-        println!("Finished sw6");
     }
 
     #[test]
@@ -731,7 +743,15 @@ mod tests {
         println!("Finished bls12-377");
         full_end_to_end_equation_test::<_, PC_Bls12_381>().expect("test failed for bls12-381");
         println!("Finished bls12-381");
-        full_end_to_end_equation_test::<_, PC_SW6>().expect("test failed for SW6");
-        println!("Finished sw6");
+    }
+
+    #[test]
+    #[should_panic]
+    fn bad_degree_bound_test() {
+        use crate::tests::*;
+        bad_degree_bound_test::<_, PC_Bls12_377>().expect("test failed for bls12-377");
+        println!("Finished bls12-377");
+        bad_degree_bound_test::<_, PC_Bls12_381>().expect("test failed for bls12-381");
+        println!("Finished bls12-381");
     }
 }
-- 
GitLab