From bab04d8175695cb0d7daf5796d6fbf611414c318 Mon Sep 17 00:00:00 2001
From: Nicholas Ward <npward@berkeley.edu>
Date: Thu, 29 Oct 2020 16:10:25 -0700
Subject: [PATCH] Prepared vk/commitment types, and individual opening
 challenges (#46)

Co-authored-by: Pratyush Mishra <pratyushmishra@berkeley.edu>
Co-authored-by: weikeng <w.k@berkeley.edu>
---
 Cargo.toml                       |   9 +-
 src/data_structures.rs           |  46 ++--
 src/ipa_pc/data_structures.rs    |  20 ++
 src/ipa_pc/mod.rs                |  81 ++++---
 src/kzg10/data_structures.rs     |  66 ++++++
 src/kzg10/mod.rs                 |   6 +-
 src/lib.rs                       | 382 ++++++++++++++++++++++---------
 src/marlin_pc/data_structures.rs |  95 +++++++-
 src/marlin_pc/mod.rs             | 254 +++++++++++++-------
 src/sonic_pc/data_structures.rs  |  33 ++-
 src/sonic_pc/mod.rs              |  81 ++++---
 11 files changed, 792 insertions(+), 281 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index f13bdc4..988c86b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -24,9 +24,12 @@ edition = "2018"
 ark-serialize = { git = "https://github.com/arkworks-rs/algebra", default-features = false, features = [ "derive" ] }
 ark-ff = { git = "https://github.com/arkworks-rs/algebra", default-features = false }
 ark-ec = { git = "https://github.com/arkworks-rs/algebra", default-features = false }
-ark-std = { git = "https://github.com/arkworks-rs/utils", default-features = false }
 ark-poly = { git = "https://github.com/arkworks-rs/algebra", default-features = false }
-bench-utils = { git = "https://github.com/arkworks-rs/utils" }
+
+ark-std = { git = "https://github.com/arkworks-rs/utils", default-features = false }
+
+bench-utils = { git = "https://github.com/arkworks-rs/utils", default-features = false  }
+
 rand_core = { version = "0.5", default-features = false }
 digest = "0.8"
 rayon = { version = "1", optional = true }
@@ -52,7 +55,7 @@ incremental = true
 debug = true
 
 [features]
-default = ["std", "parallel"]
+default = [ "std", "parallel" ]
 std = [ "ark-ff/std", "ark-ec/std", "ark-poly/std", "ark-std/std", "ark-serialize/std" ]
 print-trace = [ "bench-utils/print-trace" ]
 parallel = [ "std", "ark-ff/parallel", "ark-ec/parallel", "ark-poly/parallel", "ark-std/parallel", "rayon" ]
diff --git a/src/data_structures.rs b/src/data_structures.rs
index c62ae65..2e8e4fd 100644
--- a/src/data_structures.rs
+++ b/src/data_structures.rs
@@ -1,4 +1,4 @@
-use crate::{Cow, String, Vec};
+use crate::{Rc, String, Vec};
 use ark_ff::Field;
 pub use ark_poly::DensePolynomial as Polynomial;
 use core::borrow::Borrow;
@@ -37,6 +37,13 @@ pub trait PCVerifierKey: Clone + core::fmt::Debug {
     fn supported_degree(&self) -> usize;
 }
 
+/// Defines the minimal interface of prepared verifier keys for any polynomial
+/// commitment scheme.
+pub trait PCPreparedVerifierKey<Unprepared: PCVerifierKey> {
+    /// prepare
+    fn prepare(vk: &Unprepared) -> Self;
+}
+
 /// Defines the minimal interface of commitments for any polynomial
 /// commitment scheme.
 pub trait PCCommitment: Clone + ark_ff::ToBytes {
@@ -50,6 +57,13 @@ pub trait PCCommitment: Clone + ark_ff::ToBytes {
     fn size_in_bytes(&self) -> usize;
 }
 
+/// Defines the minimal interface of prepared commitments for any polynomial
+/// commitment scheme.
+pub trait PCPreparedCommitment<UNPREPARED: PCCommitment>: Clone {
+    /// prepare
+    fn prepare(comm: &UNPREPARED) -> Self;
+}
+
 /// Defines the minimal interface of commitment randomness for any polynomial
 /// commitment scheme.
 pub trait PCRandomness: Clone {
@@ -74,14 +88,14 @@ pub trait PCProof: Clone + ark_ff::ToBytes {
 /// maximum number of queries that will be made to it. This latter number determines
 /// the amount of protection that will be provided to a commitment for this polynomial.
 #[derive(Debug, Clone)]
-pub struct LabeledPolynomial<'a, F: Field> {
+pub struct LabeledPolynomial<F: Field> {
     label: PolynomialLabel,
-    polynomial: Cow<'a, Polynomial<F>>,
+    polynomial: Rc<Polynomial<F>>,
     degree_bound: Option<usize>,
     hiding_bound: Option<usize>,
 }
 
-impl<'a, F: Field> core::ops::Deref for LabeledPolynomial<'a, F> {
+impl<'a, F: Field> core::ops::Deref for LabeledPolynomial<F> {
     type Target = Polynomial<F>;
 
     fn deref(&self) -> &Self::Target {
@@ -89,33 +103,17 @@ impl<'a, F: Field> core::ops::Deref for LabeledPolynomial<'a, F> {
     }
 }
 
-impl<'a, F: Field> LabeledPolynomial<'a, F> {
-    /// Construct a new labeled polynomial by consuming `polynomial`.
-    pub fn new_owned(
-        label: PolynomialLabel,
-        polynomial: Polynomial<F>,
-        degree_bound: Option<usize>,
-        hiding_bound: Option<usize>,
-    ) -> Self {
-        Self {
-            label,
-            polynomial: Cow::Owned(polynomial),
-            degree_bound,
-
-            hiding_bound,
-        }
-    }
-
+impl<'a, F: Field> LabeledPolynomial<F> {
     /// Construct a new labeled polynomial.
     pub fn new(
         label: PolynomialLabel,
-        polynomial: &'a Polynomial<F>,
+        polynomial: Polynomial<F>,
         degree_bound: Option<usize>,
         hiding_bound: Option<usize>,
     ) -> Self {
         Self {
             label,
-            polynomial: Cow::Borrowed(polynomial),
+            polynomial: Rc::new(polynomial),
             degree_bound,
             hiding_bound,
         }
@@ -262,7 +260,7 @@ pub struct LinearCombination<F> {
     /// The label.
     pub label: String,
     /// The linear combination of `(coeff, poly_label)` pairs.
-    terms: Vec<(F, LCTerm)>,
+    pub terms: Vec<(F, LCTerm)>,
 }
 
 impl<F: Field> LinearCombination<F> {
diff --git a/src/ipa_pc/data_structures.rs b/src/ipa_pc/data_structures.rs
index b5efd58..2e1b5c0 100644
--- a/src/ipa_pc/data_structures.rs
+++ b/src/ipa_pc/data_structures.rs
@@ -72,6 +72,16 @@ impl<G: AffineCurve> PCVerifierKey for VerifierKey<G> {
     }
 }
 
+/// Nothing to do to prepare this verifier key (for now).
+pub type PreparedVerifierKey<G> = VerifierKey<G>;
+
+impl<G: AffineCurve> PCPreparedVerifierKey<VerifierKey<G>> for PreparedVerifierKey<G> {
+    /// prepare `PreparedVerifierKey` from `VerifierKey`
+    fn prepare(vk: &VerifierKey<G>) -> Self {
+        vk.clone()
+    }
+}
+
 /// Commitment to a polynomial that optionally enforces a degree bound.
 #[derive(Derivative)]
 #[derivative(
@@ -124,6 +134,16 @@ impl<G: AffineCurve> ToBytes for Commitment<G> {
     }
 }
 
+/// Nothing to do to prepare this commitment (for now).
+pub type PreparedCommitment<E> = Commitment<E>;
+
+impl<G: AffineCurve> PCPreparedCommitment<Commitment<G>> for PreparedCommitment<G> {
+    /// prepare `PreparedCommitment` from `Commitment`
+    fn prepare(vk: &Commitment<G>) -> Self {
+        vk.clone()
+    }
+}
+
 /// `Randomness` hides the polynomial inside a commitment and is outputted by `InnerProductArg::commit`.
 #[derive(Derivative)]
 #[derivative(
diff --git a/src/ipa_pc/mod.rs b/src/ipa_pc/mod.rs
index 1a08a90..36c1285 100644
--- a/src/ipa_pc/mod.rs
+++ b/src/ipa_pc/mod.rs
@@ -88,7 +88,7 @@ impl<G: AffineCurve, D: Digest> InnerProductArgPC<G, D> {
         point: G::ScalarField,
         values: impl IntoIterator<Item = G::ScalarField>,
         proof: &Proof<G>,
-        opening_challenge: G::ScalarField,
+        opening_challenges: &dyn Fn(u64) -> G::ScalarField,
     ) -> Option<SuccinctCheckPolynomial<G::ScalarField>> {
         let check_time = start_timer!(|| "Succinct checking");
 
@@ -100,7 +100,10 @@ impl<G: AffineCurve, D: Digest> InnerProductArgPC<G, D> {
         let mut combined_commitment_proj = G::Projective::zero();
         let mut combined_v = G::ScalarField::zero();
 
-        let mut cur_challenge = opening_challenge;
+        let mut opening_challenge_counter = 0;
+        let mut cur_challenge = opening_challenges(opening_challenge_counter);
+        opening_challenge_counter += 1;
+
         let labeled_commitments = commitments.into_iter();
         let values = values.into_iter();
 
@@ -108,7 +111,8 @@ impl<G: AffineCurve, D: Digest> InnerProductArgPC<G, D> {
             let commitment = labeled_commitment.commitment();
             combined_v += &(cur_challenge * &value);
             combined_commitment_proj += &labeled_commitment.commitment().comm.mul(cur_challenge);
-            cur_challenge *= &opening_challenge;
+            cur_challenge = opening_challenges(opening_challenge_counter);
+            opening_challenge_counter += 1;
 
             let degree_bound = labeled_commitment.degree_bound();
             assert_eq!(degree_bound.is_some(), commitment.shifted_comm.is_some());
@@ -119,7 +123,8 @@ impl<G: AffineCurve, D: Digest> InnerProductArgPC<G, D> {
                 combined_commitment_proj += &commitment.shifted_comm.unwrap().mul(cur_challenge);
             }
 
-            cur_challenge *= &opening_challenge;
+            cur_challenge = opening_challenges(opening_challenge_counter);
+            opening_challenge_counter += 1;
         }
 
         let mut combined_commitment = combined_commitment_proj.into_affine();
@@ -306,7 +311,9 @@ impl<G: AffineCurve, D: Digest> PolynomialCommitment<G::ScalarField> for InnerPr
     type UniversalParams = UniversalParams<G>;
     type CommitterKey = CommitterKey<G>;
     type VerifierKey = VerifierKey<G>;
+    type PreparedVerifierKey = PreparedVerifierKey<G>;
     type Commitment = Commitment<G>;
+    type PreparedCommitment = PreparedCommitment<G>;
     type Randomness = Randomness<G>;
     type Proof = Proof<G>;
     type BatchProof = Vec<Self::Proof>;
@@ -372,7 +379,7 @@ impl<G: AffineCurve, D: Digest> PolynomialCommitment<G::ScalarField> for InnerPr
     /// Outputs a commitment to `polynomial`.
     fn commit<'a>(
         ck: &Self::CommitterKey,
-        polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<'a, G::ScalarField>>,
+        polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<G::ScalarField>>,
         rng: Option<&mut dyn RngCore>,
     ) -> Result<
         (
@@ -439,12 +446,12 @@ impl<G: AffineCurve, D: Digest> PolynomialCommitment<G::ScalarField> for InnerPr
         Ok((comms, rands))
     }
 
-    fn open<'a>(
+    fn open_individual_opening_challenges<'a>(
         ck: &Self::CommitterKey,
-        labeled_polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<'a, G::ScalarField>>,
+        labeled_polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<G::ScalarField>>,
         commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
         point: G::ScalarField,
-        opening_challenge: G::ScalarField,
+        opening_challenges: &dyn Fn(u64) -> G::ScalarField,
         rands: impl IntoIterator<Item = &'a Self::Randomness>,
         rng: Option<&mut dyn RngCore>,
     ) -> Result<Self::Proof, Self::Error>
@@ -463,7 +470,11 @@ impl<G: AffineCurve, D: Digest> PolynomialCommitment<G::ScalarField> for InnerPr
         let comms_iter = commitments.into_iter();
 
         let combine_time = start_timer!(|| "Combining polynomials, randomness, and commitments.");
-        let mut cur_challenge = opening_challenge;
+
+        let mut opening_challenge_counter = 0;
+        let mut cur_challenge = opening_challenges(opening_challenge_counter);
+        opening_challenge_counter += 1;
+
         for (labeled_polynomial, (labeled_commitment, randomness)) in
             polys_iter.zip(comms_iter.zip(rands_iter))
         {
@@ -484,7 +495,8 @@ impl<G: AffineCurve, D: Digest> PolynomialCommitment<G::ScalarField> for InnerPr
                 combined_rand += &(cur_challenge * &randomness.rand);
             }
 
-            cur_challenge *= &opening_challenge;
+            cur_challenge = opening_challenges(opening_challenge_counter);
+            opening_challenge_counter += 1;
 
             let has_degree_bound = degree_bound.is_some();
 
@@ -517,7 +529,8 @@ impl<G: AffineCurve, D: Digest> PolynomialCommitment<G::ScalarField> for InnerPr
                 }
             }
 
-            cur_challenge *= &opening_challenge;
+            cur_challenge = opening_challenges(opening_challenge_counter);
+            opening_challenge_counter += 1;
         }
 
         end_timer!(combine_time);
@@ -677,13 +690,13 @@ impl<G: AffineCurve, D: Digest> PolynomialCommitment<G::ScalarField> for InnerPr
         })
     }
 
-    fn check<'a>(
+    fn check_individual_opening_challenges<'a>(
         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,
+        opening_challenges: &dyn Fn(u64) -> G::ScalarField,
         _rng: Option<&mut dyn RngCore>,
     ) -> Result<bool, Self::Error>
     where
@@ -707,7 +720,7 @@ impl<G: AffineCurve, D: Digest> PolynomialCommitment<G::ScalarField> for InnerPr
         }
 
         let check_poly =
-            Self::succinct_check(vk, commitments, point, values, proof, opening_challenge);
+            Self::succinct_check(vk, commitments, point, values, proof, opening_challenges);
 
         if check_poly.is_none() {
             return Ok(false);
@@ -728,13 +741,13 @@ impl<G: AffineCurve, D: Digest> PolynomialCommitment<G::ScalarField> for InnerPr
         Ok(true)
     }
 
-    fn batch_check<'a, R: RngCore>(
+    fn batch_check_individual_opening_challenges<'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,
+        opening_challenges: &dyn Fn(u64) -> G::ScalarField,
         rng: &mut R,
     ) -> Result<bool, Self::Error>
     where
@@ -743,9 +756,11 @@ impl<G: AffineCurve, D: Digest> PolynomialCommitment<G::ScalarField> for InnerPr
         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);
+        for (label, (point_label, point)) in query_set.iter() {
+            let labels = query_to_labels_map
+                .entry(point_label)
+                .or_insert((point, BTreeSet::new()));
+            labels.1.insert(label);
         }
 
         assert_eq!(proof.len(), query_to_labels_map.len());
@@ -755,7 +770,7 @@ impl<G: AffineCurve, D: Digest> PolynomialCommitment<G::ScalarField> for InnerPr
         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) {
+        for ((_point_label, (point, 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();
@@ -766,7 +781,7 @@ impl<G: AffineCurve, D: Digest> PolynomialCommitment<G::ScalarField> for InnerPr
                 })?;
 
                 let v_i = values
-                    .get(&(label.clone(), *query))
+                    .get(&(label.clone(), *point))
                     .ok_or(Error::MissingEvaluation {
                         label: label.to_string(),
                     })?;
@@ -778,10 +793,10 @@ impl<G: AffineCurve, D: Digest> PolynomialCommitment<G::ScalarField> for InnerPr
             let check_poly = Self::succinct_check(
                 vk,
                 comms.into_iter(),
-                *query,
+                *point,
                 vals.into_iter(),
                 p,
-                opening_challenge,
+                opening_challenges,
             );
 
             if check_poly.is_none() {
@@ -813,13 +828,13 @@ impl<G: AffineCurve, D: Digest> PolynomialCommitment<G::ScalarField> for InnerPr
         Ok(true)
     }
 
-    fn open_combinations<'a>(
+    fn open_combinations_individual_opening_challenges<'a>(
         ck: &Self::CommitterKey,
         lc_s: impl IntoIterator<Item = &'a LinearCombination<G::ScalarField>>,
-        polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<'a, G::ScalarField>>,
+        polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<G::ScalarField>>,
         commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
         query_set: &QuerySet<G::ScalarField>,
-        opening_challenge: G::ScalarField,
+        opening_challenges: &dyn Fn(u64) -> G::ScalarField,
         rands: impl IntoIterator<Item = &'a Self::Randomness>,
         rng: Option<&mut dyn RngCore>,
     ) -> Result<BatchLCProof<G::ScalarField, Self>, Self::Error>
@@ -891,7 +906,7 @@ impl<G: AffineCurve, D: Digest> PolynomialCommitment<G::ScalarField> for InnerPr
             }
 
             let lc_poly =
-                LabeledPolynomial::new_owned(lc_label.clone(), poly, degree_bound, hiding_bound);
+                LabeledPolynomial::new(lc_label.clone(), poly, degree_bound, hiding_bound);
             lc_polynomials.push(lc_poly);
             lc_randomness.push(Randomness {
                 rand: combined_rand,
@@ -908,12 +923,12 @@ impl<G: AffineCurve, D: Digest> PolynomialCommitment<G::ScalarField> for InnerPr
 
         let lc_commitments = Self::construct_labeled_commitments(&lc_info, &lc_commitments);
 
-        let proof = Self::batch_open(
+        let proof = Self::batch_open_individual_opening_challenges(
             ck,
             lc_polynomials.iter(),
             lc_commitments.iter(),
             &query_set,
-            opening_challenge,
+            opening_challenges,
             lc_randomness.iter(),
             rng,
         )?;
@@ -922,14 +937,14 @@ impl<G: AffineCurve, D: Digest> PolynomialCommitment<G::ScalarField> for InnerPr
 
     /// Checks that `values` are the true evaluations at `query_set` of the polynomials
     /// committed in `labeled_commitments`.
-    fn check_combinations<'a, R: RngCore>(
+    fn check_combinations_individual_opening_challenges<'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,
+        opening_challenges: &dyn Fn(u64) -> G::ScalarField,
         rng: &mut R,
     ) -> Result<bool, Self::Error>
     where
@@ -996,13 +1011,13 @@ impl<G: AffineCurve, D: Digest> PolynomialCommitment<G::ScalarField> for InnerPr
 
         let lc_commitments = Self::construct_labeled_commitments(&lc_info, &lc_commitments);
 
-        Self::batch_check(
+        Self::batch_check_individual_opening_challenges(
             vk,
             &lc_commitments,
             &query_set,
             &evaluations,
             proof,
-            opening_challenge,
+            opening_challenges,
             rng,
         )
     }
diff --git a/src/kzg10/data_structures.rs b/src/kzg10/data_structures.rs
index 0d11799..62eb2df 100644
--- a/src/kzg10/data_structures.rs
+++ b/src/kzg10/data_structures.rs
@@ -1,6 +1,7 @@
 use crate::*;
 use ark_ec::{AffineCurve, PairingEngine, ProjectiveCurve};
 use ark_ff::{PrimeField, ToBytes, Zero};
+use ark_std::borrow::Cow;
 use core::ops::{Add, AddAssign};
 
 /// `UniversalParams` are the universal parameters for the KZG10 scheme.
@@ -86,6 +87,39 @@ impl<E: PairingEngine> ToBytes for VerifierKey<E> {
     }
 }
 
+/// `PreparedVerifierKey` is the fully prepared version for checking evaluation proofs for a given commitment.
+/// We omit gamma here for simplicity.
+#[derive(Derivative)]
+#[derivative(Default(bound = ""), Clone(bound = ""), Debug(bound = ""))]
+pub struct PreparedVerifierKey<E: PairingEngine> {
+    /// The generator of G1, prepared for power series.
+    pub prepared_g: Vec<E::G1Affine>,
+    /// The generator of G2, prepared for use in pairings.
+    pub prepared_h: E::G2Prepared,
+    /// \beta times the above generator of G2, prepared for use in pairings.
+    pub prepared_beta_h: E::G2Prepared,
+}
+
+impl<E: PairingEngine> PreparedVerifierKey<E> {
+    /// prepare `PreparedVerifierKey` from `VerifierKey`
+    pub fn prepare(vk: &VerifierKey<E>) -> Self {
+        let supported_bits = E::Fr::size_in_bits();
+
+        let mut prepared_g = Vec::<E::G1Affine>::new();
+        let mut g = E::G1Projective::from(vk.g.clone());
+        for _ in 0..supported_bits {
+            prepared_g.push(g.clone().into());
+            g.double_in_place();
+        }
+
+        Self {
+            prepared_g,
+            prepared_h: vk.prepared_h.clone(),
+            prepared_beta_h: vk.prepared_beta_h.clone(),
+        }
+    }
+}
+
 /// `Commitment` commits to a polynomial. It is output by `KZG10::commit`.
 #[derive(Derivative)]
 #[derivative(
@@ -133,6 +167,38 @@ impl<'a, E: PairingEngine> AddAssign<(E::Fr, &'a Commitment<E>)> for Commitment<
     }
 }
 
+/// `PreparedCommitment` commits to a polynomial and prepares for mul_bits.
+#[derive(Derivative)]
+#[derivative(
+    Default(bound = ""),
+    Hash(bound = ""),
+    Clone(bound = ""),
+    Debug(bound = ""),
+    PartialEq(bound = ""),
+    Eq(bound = "")
+)]
+pub struct PreparedCommitment<E: PairingEngine>(
+    /// The commitment is a group element.
+    pub Vec<E::G1Affine>,
+);
+
+impl<E: PairingEngine> PreparedCommitment<E> {
+    /// prepare `PreparedCommitment` from `Commitment`
+    pub fn prepare(comm: &Commitment<E>) -> Self {
+        let mut prepared_comm = Vec::<E::G1Affine>::new();
+        let mut cur = E::G1Projective::from(comm.0.clone());
+
+        let supported_bits = E::Fr::size_in_bits();
+
+        for _ in 0..supported_bits {
+            prepared_comm.push(cur.clone().into());
+            cur.double_in_place();
+        }
+
+        Self { 0: prepared_comm }
+    }
+}
+
 /// `Randomness` hides the polynomial inside a commitment. It is output by `KZG10::commit`.
 #[derive(Derivative)]
 #[derivative(
diff --git a/src/kzg10/mod.rs b/src/kzg10/mod.rs
index 5da4002..f07c65e 100644
--- a/src/kzg10/mod.rs
+++ b/src/kzg10/mod.rs
@@ -421,7 +421,7 @@ impl<E: PairingEngine> KZG10<E> {
         supported_degree: usize,
         max_degree: usize,
         enforced_degree_bounds: Option<&[usize]>,
-        p: &'a LabeledPolynomial<'a, E::Fr>,
+        p: &'a LabeledPolynomial<E::Fr>,
     ) -> Result<(), Error> {
         if let Some(bound) = p.degree_bound() {
             let enforced_degree_bounds =
@@ -494,8 +494,8 @@ mod tests {
                 .collect();
 
             let powers = Powers {
-                powers_of_g: Cow::Owned(powers_of_g),
-                powers_of_gamma_g: Cow::Owned(powers_of_gamma_g),
+                powers_of_g: ark_std::borrow::Cow::Owned(powers_of_g),
+                powers_of_gamma_g: ark_std::borrow::Cow::Owned(powers_of_gamma_g),
             };
             let vk = VerifierKey {
                 g: pp.powers_of_g[0],
diff --git a/src/lib.rs b/src/lib.rs
index 581a4de..4be0f24 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -17,12 +17,12 @@ extern crate bench_utils;
 
 use ark_ff::Field;
 pub use ark_poly::DensePolynomial as Polynomial;
+use core::iter::FromIterator;
 use rand_core::RngCore;
 
 use ark_std::{
-    borrow::Cow,
     collections::{BTreeMap, BTreeSet},
-    iter::FromIterator,
+    rc::Rc,
     string::{String, ToString},
     vec::Vec,
 };
@@ -44,6 +44,11 @@ macro_rules! eprintln {
     () => {};
     ($($arg: tt)*) => {};
 }
+#[cfg(not(feature = "std"))]
+macro_rules! println {
+    () => {};
+    ($($arg: tt)*) => {};
+}
 /// The core [[KZG10]][kzg] construction.
 ///
 /// [kzg]: http://cacr.uwaterloo.ca/techreports/2010/cacr2010-10.pdf
@@ -77,10 +82,11 @@ pub mod sonic_pc;
 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)`
-/// pair, where `label` is the label of a polynomial in `p`, and `query` is the field element
+/// `p` that have previously been committed to. Each element of a `QuerySet` is a pair of
+/// `(label, (point_label, point))`, where `label` is the label of a polynomial in `p`,
+/// `point_label` is the label for the point (e.g., "beta"), and  and `point` is the field element
 /// that `p[label]` is to be queried at.
-pub type QuerySet<'a, F> = BTreeSet<(String, F)>;
+pub type QuerySet<'a, F> = BTreeSet<(String, (String, F))>;
 
 /// `Evaluations` is the result of querying a set of labeled polynomials or equations
 /// `p` at a `QuerySet` `Q`. It maps each element of `Q` to the resulting evaluation.
@@ -89,6 +95,7 @@ pub type QuerySet<'a, F> = BTreeSet<(String, F)>;
 pub type Evaluations<'a, F> = BTreeMap<(String, F), F>;
 
 /// A proof of satisfaction of linear combinations.
+#[derive(Clone)]
 pub struct BatchLCProof<F: Field, PC: PolynomialCommitment<F>> {
     /// Evaluation proof.
     pub proof: PC::BatchProof,
@@ -109,8 +116,12 @@ pub trait PolynomialCommitment<F: Field>: Sized {
     type CommitterKey: PCCommitterKey;
     /// The verifier key for the scheme; used to check an evaluation proof.
     type VerifierKey: PCVerifierKey;
+    /// The prepared verifier key for the scheme; used to check an evaluation proof.
+    type PreparedVerifierKey: PCPreparedVerifierKey<Self::VerifierKey> + Clone;
     /// The commitment to a polynomial.
-    type Commitment: PCCommitment;
+    type Commitment: PCCommitment + Default;
+    /// The prepared commitment to a polynomial.
+    type PreparedCommitment: PCPreparedCommitment<Self::Commitment>;
     /// The commitment randomness.
     type Randomness: PCRandomness;
     /// The evaluation proof for a single point.
@@ -147,7 +158,7 @@ pub trait PolynomialCommitment<F: Field>: Sized {
     /// polynomial will have the corresponding degree bound enforced.
     fn commit<'a>(
         ck: &Self::CommitterKey,
-        polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<'a, F>>,
+        polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<F>>,
         rng: Option<&mut dyn RngCore>,
     ) -> Result<
         (
@@ -161,7 +172,7 @@ pub trait PolynomialCommitment<F: Field>: Sized {
     /// of the polynomials at the query point.
     fn open<'a>(
         ck: &Self::CommitterKey,
-        labeled_polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<'a, F>>,
+        labeled_polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<F>>,
         commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
         point: F,
         opening_challenge: F,
@@ -170,13 +181,25 @@ pub trait PolynomialCommitment<F: Field>: Sized {
     ) -> Result<Self::Proof, Self::Error>
     where
         Self::Randomness: 'a,
-        Self::Commitment: 'a;
+        Self::Commitment: 'a,
+    {
+        let opening_challenges = |pow| opening_challenge.pow(&[pow]);
+        Self::open_individual_opening_challenges(
+            ck,
+            labeled_polynomials,
+            commitments,
+            point,
+            &opening_challenges,
+            rands,
+            rng,
+        )
+    }
 
     /// 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>>,
+        labeled_polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<F>>,
         commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
         query_set: &QuerySet<F>,
         opening_challenge: F,
@@ -187,62 +210,16 @@ pub trait PolynomialCommitment<F: Field>: Sized {
         Self::Randomness: 'a,
         Self::Commitment: 'a,
     {
-        let rng = &mut crate::optional_rng::OptionalRng(rng);
-        let poly_rand_comm: BTreeMap<_, _> = labeled_polynomials
-            .into_iter()
-            .zip(rands)
-            .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 {}",
-            poly_rand_comm.len(),
-            query_set.len(),
-        ));
-
-        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);
-        }
-
-        let mut proofs = Vec::new();
-        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, 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_comms,
-                *query,
-                opening_challenge,
-                query_rands,
-                Some(rng),
-            )?;
-
-            end_timer!(proof_time);
-
-            proofs.push(proof);
-        }
-        end_timer!(open_time);
-
-        Ok(proofs.into())
+        let opening_challenges = |pow| opening_challenge.pow(&[pow]);
+        Self::batch_open_individual_opening_challenges(
+            ck,
+            labeled_polynomials,
+            commitments,
+            query_set,
+            &opening_challenges,
+            rands,
+            rng,
+        )
     }
 
     /// Verifies that `values` are the evaluations at `point` of the polynomials
@@ -257,7 +234,19 @@ pub trait PolynomialCommitment<F: Field>: Sized {
         rng: Option<&mut dyn RngCore>,
     ) -> Result<bool, Self::Error>
     where
-        Self::Commitment: 'a;
+        Self::Commitment: 'a,
+    {
+        let opening_challenges = |pow| opening_challenge.pow(&[pow]);
+        Self::check_individual_opening_challenges(
+            vk,
+            commitments,
+            point,
+            values,
+            proof,
+            &opening_challenges,
+            rng,
+        )
+    }
 
     /// Checks that `values` are the true evaluations at `query_set` of the polynomials
     /// committed in `labeled_commitments`.
@@ -270,14 +259,126 @@ pub trait PolynomialCommitment<F: Field>: Sized {
         opening_challenge: F,
         rng: &mut R,
     ) -> Result<bool, Self::Error>
+    where
+        Self::Commitment: 'a,
+    {
+        let opening_challenges = |pow| opening_challenge.pow(&[pow]);
+        Self::batch_check_individual_opening_challenges(
+            vk,
+            commitments,
+            query_set,
+            evaluations,
+            proof,
+            &opening_challenges,
+            rng,
+        )
+    }
+
+    /// On input a list of polynomials, linear combinations of those polynomials,
+    /// and a query set, `open_combination` outputs a proof of evaluation of
+    /// the combinations at the points in the query set.
+    fn open_combinations<'a>(
+        ck: &Self::CommitterKey,
+        linear_combinations: impl IntoIterator<Item = &'a LinearCombination<F>>,
+        polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<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 opening_challenges = |pow| opening_challenge.pow(&[pow]);
+        Self::open_combinations_individual_opening_challenges(
+            ck,
+            linear_combinations,
+            polynomials,
+            commitments,
+            query_set,
+            &opening_challenges,
+            rands,
+            rng,
+        )
+    }
+
+    /// Checks that `evaluations` are the true evaluations at `query_set` of the
+    /// linear combinations of polynomials committed in `commitments`.
+    fn check_combinations<'a, R: RngCore>(
+        vk: &Self::VerifierKey,
+        linear_combinations: impl IntoIterator<Item = &'a LinearCombination<F>>,
+        commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
+        eqn_query_set: &QuerySet<F>,
+        eqn_evaluations: &Evaluations<F>,
+        proof: &BatchLCProof<F, Self>,
+        opening_challenge: F,
+        rng: &mut R,
+    ) -> Result<bool, Self::Error>
+    where
+        Self::Commitment: 'a,
+    {
+        let opening_challenges = |pow| opening_challenge.pow(&[pow]);
+        Self::check_combinations_individual_opening_challenges(
+            vk,
+            linear_combinations,
+            commitments,
+            eqn_query_set,
+            eqn_evaluations,
+            proof,
+            &opening_challenges,
+            rng,
+        )
+    }
+
+    /// open but with individual challenges
+    fn open_individual_opening_challenges<'a>(
+        ck: &Self::CommitterKey,
+        labeled_polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<F>>,
+        commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
+        point: F,
+        opening_challenges: &dyn Fn(u64) -> F,
+        rands: impl IntoIterator<Item = &'a Self::Randomness>,
+        rng: Option<&mut dyn RngCore>,
+    ) -> Result<Self::Proof, Self::Error>
+    where
+        Self::Randomness: 'a,
+        Self::Commitment: 'a;
+
+    /// check but with individual challenges
+    fn check_individual_opening_challenges<'a>(
+        vk: &Self::VerifierKey,
+        commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
+        point: F,
+        values: impl IntoIterator<Item = F>,
+        proof: &Self::Proof,
+        opening_challenges: &dyn Fn(u64) -> F,
+        rng: Option<&mut dyn RngCore>,
+    ) -> Result<bool, Self::Error>
+    where
+        Self::Commitment: 'a;
+
+    /// batch_check but with individual challenges
+    fn batch_check_individual_opening_challenges<'a, R: RngCore>(
+        vk: &Self::VerifierKey,
+        commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
+        query_set: &QuerySet<F>,
+        evaluations: &Evaluations<F>,
+        proof: &Self::BatchProof,
+        opening_challenges: &dyn Fn(u64) -> F,
+        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);
+        for (label, (point_label, point)) in query_set.iter() {
+            let labels = query_to_labels_map
+                .entry(point_label)
+                .or_insert((point, BTreeSet::new()));
+            labels.1.insert(label);
         }
 
         // Implicit assumption: proofs are order in same manner as queries in
@@ -286,7 +387,8 @@ pub trait PolynomialCommitment<F: Field>: Sized {
         assert_eq!(proofs.len(), query_to_labels_map.len());
 
         let mut result = true;
-        for ((query, labels), proof) in query_to_labels_map.into_iter().zip(proofs) {
+        for ((_point_label, (point, labels)), proof) in query_to_labels_map.into_iter().zip(proofs)
+        {
             let mut comms: Vec<&'_ LabeledCommitment<_>> = Vec::new();
             let mut values = Vec::new();
             for label in labels.into_iter() {
@@ -296,7 +398,7 @@ pub trait PolynomialCommitment<F: Field>: Sized {
 
                 let v_i =
                     evaluations
-                        .get(&(label.clone(), *query))
+                        .get(&(label.clone(), *point))
                         .ok_or(Error::MissingEvaluation {
                             label: label.to_string(),
                         })?;
@@ -306,13 +408,13 @@ pub trait PolynomialCommitment<F: Field>: Sized {
             }
 
             let proof_time = start_timer!(|| "Checking per-query proof");
-            result &= Self::check(
+            result &= Self::check_individual_opening_challenges(
                 vk,
                 comms,
-                *query,
+                *point,
                 values,
                 &proof,
-                opening_challenge,
+                opening_challenges,
                 Some(rng),
             )?;
             end_timer!(proof_time);
@@ -320,16 +422,14 @@ pub trait PolynomialCommitment<F: Field>: Sized {
         Ok(result)
     }
 
-    /// On input a list of polynomials, linear combinations of those polynomials,
-    /// and a query set, `open_combination` outputs a proof of evaluation of
-    /// the combinations at the points in the query set.
-    fn open_combinations<'a>(
+    /// open_combinations but with individual challenges
+    fn open_combinations_individual_opening_challenges<'a>(
         ck: &Self::CommitterKey,
         linear_combinations: impl IntoIterator<Item = &'a LinearCombination<F>>,
-        polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<'a, F>>,
+        polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<F>>,
         commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
         query_set: &QuerySet<F>,
-        opening_challenge: F,
+        opening_challenges: &dyn Fn(u64) -> F,
         rands: impl IntoIterator<Item = &'a Self::Randomness>,
         rng: Option<&mut dyn RngCore>,
     ) -> Result<BatchLCProof<F, Self>, Self::Error>
@@ -342,12 +442,12 @@ pub trait PolynomialCommitment<F: Field>: Sized {
         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(
+        let proof = Self::batch_open_individual_opening_challenges(
             ck,
             polynomials,
             commitments,
             &poly_query_set,
-            opening_challenge,
+            opening_challenges,
             rands,
             rng,
         )?;
@@ -357,16 +457,15 @@ pub trait PolynomialCommitment<F: Field>: Sized {
         })
     }
 
-    /// Checks that `evaluations` are the true evaluations at `query_set` of the
-    /// linear combinations of polynomials committed in `commitments`.
-    fn check_combinations<'a, R: RngCore>(
+    /// check_combinations with individual challenges
+    fn check_combinations_individual_opening_challenges<'a, R: RngCore>(
         vk: &Self::VerifierKey,
         linear_combinations: impl IntoIterator<Item = &'a LinearCombination<F>>,
         commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
         eqn_query_set: &QuerySet<F>,
         eqn_evaluations: &Evaluations<F>,
         proof: &BatchLCProof<F, Self>,
-        opening_challenge: F,
+        opening_challenges: &dyn Fn(u64) -> F,
         rng: &mut R,
     ) -> Result<bool, Self::Error>
     where
@@ -377,10 +476,15 @@ pub trait PolynomialCommitment<F: Field>: Sized {
         let lc_s = BTreeMap::from_iter(linear_combinations.into_iter().map(|lc| (lc.label(), lc)));
 
         let poly_query_set = lc_query_set_to_poly_query_set(lc_s.values().copied(), eqn_query_set);
-        let poly_evals =
-            Evaluations::from_iter(poly_query_set.iter().cloned().zip(evals.clone().unwrap()));
+        let poly_evals = Evaluations::from_iter(
+            poly_query_set
+                .iter()
+                .map(|(_, point)| point)
+                .cloned()
+                .zip(evals.clone().unwrap()),
+        );
 
-        for &(ref lc_label, point) in eqn_query_set {
+        for &(ref lc_label, (_, point)) in eqn_query_set {
             if let Some(lc) = lc_s.get(lc_label) {
                 let claimed_rhs = *eqn_evaluations.get(&(lc_label.clone(), point)).ok_or(
                     Error::MissingEvaluation {
@@ -407,13 +511,13 @@ pub trait PolynomialCommitment<F: Field>: Sized {
             }
         }
 
-        let pc_result = Self::batch_check(
+        let pc_result = Self::batch_check_individual_opening_challenges(
             vk,
             commitments,
             &poly_query_set,
             &poly_evals,
             proof,
-            opening_challenge,
+            opening_challenges,
             rng,
         )?;
         if !pc_result {
@@ -423,16 +527,90 @@ pub trait PolynomialCommitment<F: Field>: Sized {
 
         Ok(true)
     }
+
+    /// batch_open with individual challenges
+    fn batch_open_individual_opening_challenges<'a>(
+        ck: &Self::CommitterKey,
+        labeled_polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<F>>,
+        commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
+        query_set: &QuerySet<F>,
+        opening_challenges: &dyn Fn(u64) -> 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 rng = &mut crate::optional_rng::OptionalRng(rng);
+        let poly_rand_comm: BTreeMap<_, _> = labeled_polynomials
+            .into_iter()
+            .zip(rands)
+            .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 {}",
+            poly_rand_comm.len(),
+            query_set.len(),
+        ));
+
+        let mut query_to_labels_map = BTreeMap::new();
+
+        for (label, (point_label, point)) in query_set.iter() {
+            let labels = query_to_labels_map
+                .entry(point_label)
+                .or_insert((point, BTreeSet::new()));
+            labels.1.insert(label);
+        }
+
+        let mut proofs = Vec::new();
+        for (_point_label, (point, labels)) in query_to_labels_map.into_iter() {
+            let mut query_polys: Vec<&'a LabeledPolynomial<_>> = 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, 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_individual_opening_challenges(
+                ck,
+                query_polys,
+                query_comms,
+                *point,
+                opening_challenges,
+                query_rands,
+                Some(rng),
+            )?;
+
+            end_timer!(proof_time);
+
+            proofs.push(proof);
+        }
+        end_timer!(open_time);
+
+        Ok(proofs.into())
+    }
 }
 
 /// Evaluate the given polynomials at `query_set`.
 pub fn evaluate_query_set<'a, F: Field>(
-    polys: impl IntoIterator<Item = &'a LabeledPolynomial<'a, F>>,
+    polys: impl IntoIterator<Item = &'a LabeledPolynomial<F>>,
     query_set: &QuerySet<'a, F>,
 ) -> Evaluations<'a, F> {
     let polys = BTreeMap::from_iter(polys.into_iter().map(|p| (p.label(), p)));
     let mut evaluations = Evaluations::new();
-    for (label, point) in query_set {
+    for (label, (_, point)) in query_set {
         let poly = polys
             .get(label)
             .expect("polynomial in evaluated lc is not found");
@@ -449,11 +627,11 @@ fn lc_query_set_to_poly_query_set<'a, F: 'a + Field>(
     let mut poly_query_set = QuerySet::new();
     let lc_s = linear_combinations.into_iter().map(|lc| (lc.label(), lc));
     let linear_combinations = BTreeMap::from_iter(lc_s);
-    for (lc_label, point) in query_set {
+    for (lc_label, (point_label, point)) in query_set {
         if let Some(lc) = linear_combinations.get(lc_label) {
             for (_, poly_label) in lc.iter().filter(|(_, l)| !l.is_one()) {
                 if let LCTerm::PolyLabel(l) = poly_label {
-                    poly_query_set.insert((l.into(), *point));
+                    poly_query_set.insert((l.into(), (point_label.clone(), *point)));
                 }
             }
         }
@@ -507,7 +685,7 @@ pub mod tests {
                 let hiding_bound = Some(1);
                 degree_bounds.push(degree_bound);
 
-                polynomials.push(LabeledPolynomial::new_owned(
+                polynomials.push(LabeledPolynomial::new(
                     label,
                     poly,
                     Some(degree_bound),
@@ -536,7 +714,7 @@ pub mod tests {
             let mut values = Evaluations::new();
             let point = F::rand(rng);
             for (i, label) in labels.iter().enumerate() {
-                query_set.insert((label.clone(), point));
+                query_set.insert((label.clone(), (format!("{}", i), point)));
                 let value = polynomials[i].evaluate(point);
                 values.insert((label.clone(), point), value);
             }
@@ -628,7 +806,7 @@ pub mod tests {
                 };
                 println!("Hiding bound: {:?}", hiding_bound);
 
-                polynomials.push(LabeledPolynomial::new_owned(
+                polynomials.push(LabeledPolynomial::new(
                     label,
                     poly,
                     degree_bound,
@@ -660,7 +838,7 @@ pub mod tests {
             for _ in 0..num_points_in_query_set {
                 let point = F::rand(rng);
                 for (i, label) in labels.iter().enumerate() {
-                    query_set.insert((label.clone(), point));
+                    query_set.insert((label.clone(), (format!("{}", i), point)));
                     let value = polynomials[i].evaluate(point);
                     values.insert((label.clone(), point), value);
                 }
@@ -767,7 +945,7 @@ pub mod tests {
                 };
                 println!("Hiding bound: {:?}", hiding_bound);
 
-                polynomials.push(LabeledPolynomial::new_owned(
+                polynomials.push(LabeledPolynomial::new(
                     label,
                     poly,
                     degree_bound,
@@ -823,7 +1001,7 @@ pub mod tests {
                     if !lc.is_empty() {
                         linear_combinations.push(lc);
                         // Insert query
-                        query_set.insert((label.clone(), point));
+                        query_set.insert((label.clone(), (format!("{}", i), point)));
                     }
                 }
             }
diff --git a/src/marlin_pc/data_structures.rs b/src/marlin_pc/data_structures.rs
index 68d0096..c2ae690 100644
--- a/src/marlin_pc/data_structures.rs
+++ b/src/marlin_pc/data_structures.rs
@@ -1,6 +1,9 @@
-use crate::{PCCommitment, PCCommitterKey, PCRandomness, PCVerifierKey, Vec};
-use ark_ec::PairingEngine;
-use ark_ff::ToBytes;
+use crate::{
+    PCCommitment, PCCommitterKey, PCPreparedCommitment, PCPreparedVerifierKey, PCRandomness,
+    PCVerifierKey, Vec,
+};
+use ark_ec::{PairingEngine, ProjectiveCurve};
+use ark_ff::{PrimeField, ToBytes};
 use core::ops::{Add, AddAssign};
 use rand_core::RngCore;
 
@@ -143,6 +146,64 @@ impl<E: PairingEngine> ToBytes for VerifierKey<E> {
     }
 }
 
+/// `PreparedVerifierKey` is used to check evaluation proofs for a given commitment.
+#[derive(Derivative)]
+#[derivative(Clone(bound = ""), Debug(bound = ""))]
+pub struct PreparedVerifierKey<E: PairingEngine> {
+    /// The verification key for the underlying KZG10 scheme.
+    pub prepared_vk: kzg10::PreparedVerifierKey<E>,
+    /// Information required to enforce degree bounds. Each pair
+    /// is of the form `(degree_bound, shifting_advice)`.
+    /// This is `None` if `self` does not support enforcing any degree bounds.
+    pub prepared_degree_bounds_and_shift_powers: Option<Vec<(usize, Vec<E::G1Affine>)>>,
+    /// The maximum degree supported by the `UniversalParams` `self` was derived
+    /// from.
+    pub max_degree: usize,
+    /// The maximum degree supported by the trimmed parameters that `self` is
+    /// a part of.
+    pub supported_degree: usize,
+}
+
+impl<E: PairingEngine> PCPreparedVerifierKey<VerifierKey<E>> for PreparedVerifierKey<E> {
+    /// prepare `PreparedVerifierKey` from `VerifierKey`
+    fn prepare(vk: &VerifierKey<E>) -> Self {
+        let prepared_vk = kzg10::PreparedVerifierKey::<E>::prepare(&vk.vk);
+
+        let supported_bits = E::Fr::size_in_bits();
+
+        let prepared_degree_bounds_and_shift_powers: Option<Vec<(usize, Vec<E::G1Affine>)>> =
+            if vk.degree_bounds_and_shift_powers.is_some() {
+                let mut res = Vec::<(usize, Vec<E::G1Affine>)>::new();
+
+                let degree_bounds_and_shift_powers =
+                    vk.degree_bounds_and_shift_powers.as_ref().unwrap();
+
+                for (d, shift_power) in degree_bounds_and_shift_powers {
+                    let mut prepared_shift_power = Vec::<E::G1Affine>::new();
+
+                    let mut cur = E::G1Projective::from(shift_power.clone());
+                    for _ in 0..supported_bits {
+                        prepared_shift_power.push(cur.clone().into());
+                        cur.double_in_place();
+                    }
+
+                    res.push((d.clone(), prepared_shift_power));
+                }
+
+                Some(res)
+            } else {
+                None
+            };
+
+        Self {
+            prepared_vk,
+            prepared_degree_bounds_and_shift_powers,
+            max_degree: vk.max_degree,
+            supported_degree: vk.supported_degree,
+        }
+    }
+}
+
 /// Commitment to a polynomial that optionally enforces a degree bound.
 #[derive(Derivative)]
 #[derivative(
@@ -195,6 +256,34 @@ impl<E: PairingEngine> PCCommitment for Commitment<E> {
     }
 }
 
+/// Prepared commitment to a polynomial that optionally enforces a degree bound.
+#[derive(Derivative)]
+#[derivative(
+    Hash(bound = ""),
+    Clone(bound = ""),
+    Debug(bound = ""),
+    PartialEq(bound = ""),
+    Eq(bound = "")
+)]
+pub struct PreparedCommitment<E: PairingEngine> {
+    pub(crate) prepared_comm: kzg10::PreparedCommitment<E>,
+    pub(crate) shifted_comm: Option<kzg10::Commitment<E>>,
+}
+
+impl<E: PairingEngine> PCPreparedCommitment<Commitment<E>> for PreparedCommitment<E> {
+    /// Prepare commitment to a polynomial that optionally enforces a degree bound.
+    fn prepare(comm: &Commitment<E>) -> Self {
+        let prepared_comm = kzg10::PreparedCommitment::<E>::prepare(&comm.comm);
+
+        let shifted_comm = comm.shifted_comm.clone();
+
+        Self {
+            prepared_comm,
+            shifted_comm,
+        }
+    }
+}
+
 /// `Randomness` hides the polynomial inside a commitment. It is output by `KZG10::commit`.
 #[derive(Derivative)]
 #[derivative(
diff --git a/src/marlin_pc/mod.rs b/src/marlin_pc/mod.rs
index 753ba7c..301acea 100644
--- a/src/marlin_pc/mod.rs
+++ b/src/marlin_pc/mod.rs
@@ -5,7 +5,7 @@ use crate::{LabeledCommitment, LabeledPolynomial, LinearCombination};
 use crate::{PCRandomness, PCUniversalParams, Polynomial, PolynomialCommitment};
 
 use ark_ec::{AffineCurve, PairingEngine, ProjectiveCurve};
-use ark_ff::{Field, One, Zero};
+use ark_ff::{One, Zero};
 use ark_std::vec;
 use core::{convert::TryInto, marker::PhantomData};
 use rand_core::RngCore;
@@ -109,26 +109,31 @@ impl<E: PairingEngine> MarlinKZG10<E> {
     }
 
     /// Accumulate `commitments` and `values` according to `opening_challenge`.
-    fn accumulate_commitments_and_values<'a>(
+    fn accumulate_commitments_and_values_individual_opening_challenges<'a>(
         vk: &VerifierKey<E>,
         commitments: impl IntoIterator<Item = &'a LabeledCommitment<Commitment<E>>>,
         values: impl IntoIterator<Item = E::Fr>,
-        opening_challenge: E::Fr,
+        opening_challenges: &dyn Fn(u64) -> E::Fr,
     ) -> Result<(E::G1Projective, E::Fr), Error> {
         let acc_time = start_timer!(|| "Accumulating commitments and values");
         let mut combined_comm = E::G1Projective::zero();
         let mut combined_value = E::Fr::zero();
-        let mut challenge_i = E::Fr::one();
+        let mut opening_challenge_counter = 0;
         for (labeled_commitment, value) in commitments.into_iter().zip(values) {
             let degree_bound = labeled_commitment.degree_bound();
             let commitment = labeled_commitment.commitment();
             assert_eq!(degree_bound.is_some(), commitment.shifted_comm.is_some());
 
+            let challenge_i = opening_challenges(opening_challenge_counter);
+            opening_challenge_counter += 1;
+
             combined_comm += &commitment.comm.0.mul(challenge_i);
             combined_value += &(value * &challenge_i);
 
             if let Some(degree_bound) = degree_bound {
-                let challenge_i_1 = challenge_i * &opening_challenge;
+                let challenge_i_1 = opening_challenges(opening_challenge_counter);
+                opening_challenge_counter += 1;
+
                 let shifted_comm = commitment
                     .shifted_comm
                     .as_ref()
@@ -139,11 +144,12 @@ impl<E: PairingEngine> MarlinKZG10<E> {
                 let shift_power = vk
                     .get_shift_power(degree_bound)
                     .ok_or(Error::UnsupportedDegreeBound(degree_bound))?;
-                let mut adjusted_comm = shifted_comm - &shift_power.mul(value);
+
+                let mut adjusted_comm = shifted_comm - &shift_power.mul(value.clone());
+
                 adjusted_comm *= challenge_i_1;
                 combined_comm += &adjusted_comm;
             }
-            challenge_i *= &opening_challenge.square();
         }
 
         end_timer!(acc_time);
@@ -155,7 +161,9 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for MarlinKZG10<E> {
     type UniversalParams = UniversalParams<E>;
     type CommitterKey = CommitterKey<E>;
     type VerifierKey = VerifierKey<E>;
+    type PreparedVerifierKey = PreparedVerifierKey<E>;
     type Commitment = Commitment<E>;
+    type PreparedCommitment = PreparedCommitment<E>;
     type Randomness = Randomness<E>;
     type Proof = kzg10::Proof<E>;
     type BatchProof = Vec<Self::Proof>;
@@ -192,14 +200,15 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for MarlinKZG10<E> {
         let powers_of_gamma_g = (0..=supported_hiding_bound + 1)
             .map(|i| pp.powers_of_gamma_g[&i])
             .collect::<Vec<_>>();
+
         end_timer!(ck_time);
 
         // Construct the core KZG10 verifier key.
         let vk = kzg10::VerifierKey {
-            g: pp.powers_of_g[0],
+            g: pp.powers_of_g[0].clone(),
             gamma_g: pp.powers_of_gamma_g[&0],
-            h: pp.h,
-            beta_h: pp.beta_h,
+            h: pp.h.clone(),
+            beta_h: pp.beta_h.clone(),
             prepared_h: pp.prepared_h.clone(),
             prepared_beta_h: pp.prepared_beta_h.clone(),
         };
@@ -217,8 +226,11 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for MarlinKZG10<E> {
                 if enforced_degree_bounds.is_empty() {
                     (None, None)
                 } else {
+                    let mut sorted_enforced_degree_bounds = enforced_degree_bounds.clone();
+                    sorted_enforced_degree_bounds.sort();
+
                     let lowest_shifted_power = max_degree
-                        - enforced_degree_bounds
+                        - sorted_enforced_degree_bounds
                             .last()
                             .ok_or(Error::EmptyDegreeBounds)?;
 
@@ -232,7 +244,7 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for MarlinKZG10<E> {
 
                     let degree_bounds_and_shift_powers = enforced_degree_bounds
                         .iter()
-                        .map(|d| (*d, pp.powers_of_g[max_degree - d]))
+                        .map(|d| (*d, pp.powers_of_g[max_degree - *d]))
                         .collect();
                     (Some(shifted_powers), Some(degree_bounds_and_shift_powers))
                 }
@@ -260,7 +272,7 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for MarlinKZG10<E> {
     /// Outputs a commitment to `polynomial`.
     fn commit<'a>(
         ck: &Self::CommitterKey,
-        polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<'a, E::Fr>>,
+        polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<E::Fr>>,
         rng: Option<&mut dyn RngCore>,
     ) -> Result<
         (
@@ -328,18 +340,18 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for MarlinKZG10<E> {
     }
 
     /// On input a polynomial `p` and a point `point`, outputs a proof for the same.
-    fn open<'a>(
-        ck: &Self::CommitterKey,
-        labeled_polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<'a, E::Fr>>,
-        _commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
+    fn open_individual_opening_challenges<'a>(
+        ck: &CommitterKey<E>,
+        labeled_polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<E::Fr>>,
+        _commitments: impl IntoIterator<Item = &'a LabeledCommitment<Commitment<E>>>,
         point: E::Fr,
-        opening_challenge: E::Fr,
-        rands: impl IntoIterator<Item = &'a Self::Randomness>,
+        opening_challenges: &dyn Fn(u64) -> E::Fr,
+        rands: impl IntoIterator<Item = &'a Randomness<E>>,
         _rng: Option<&mut dyn RngCore>,
-    ) -> Result<Self::Proof, Self::Error>
+    ) -> Result<kzg10::Proof<E>, Error>
     where
-        Self::Randomness: 'a,
-        Self::Commitment: 'a,
+        Randomness<E>: 'a,
+        Commitment<E>: 'a,
     {
         let mut p = Polynomial::zero();
         let mut r = kzg10::Randomness::empty();
@@ -348,7 +360,8 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for MarlinKZG10<E> {
         let mut shifted_r_witness = Polynomial::zero();
 
         let mut enforce_degree_bound = false;
-        for (j, (polynomial, rand)) in labeled_polynomials.into_iter().zip(rands).enumerate() {
+        let mut opening_challenge_counter = 0;
+        for (polynomial, rand) in labeled_polynomials.into_iter().zip(rands) {
             let degree_bound = polynomial.degree_bound();
 
             let enforced_degree_bounds: Option<&[usize]> = ck
@@ -363,7 +376,8 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for MarlinKZG10<E> {
             )?;
 
             // compute challenge^j and challenge^{j+1}.
-            let challenge_j = opening_challenge.pow([2 * j as u64]);
+            let challenge_j = opening_challenges(opening_challenge_counter);
+            opening_challenge_counter += 1;
 
             assert_eq!(degree_bound.is_some(), rand.shifted_rand.is_some());
 
@@ -378,7 +392,8 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for MarlinKZG10<E> {
                     point,
                     &shifted_rand,
                 )?;
-                let challenge_j_1 = challenge_j * &opening_challenge;
+                let challenge_j_1 = opening_challenges(opening_challenge_counter);
+                opening_challenge_counter += 1;
 
                 let shifted_witness = shift_polynomial(ck, &witness, degree_bound);
 
@@ -420,52 +435,59 @@ 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>(
-        vk: &Self::VerifierKey,
-        commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
+    fn check_individual_opening_challenges<'a>(
+        vk: &VerifierKey<E>,
+        commitments: impl IntoIterator<Item = &'a LabeledCommitment<Commitment<E>>>,
         point: E::Fr,
         values: impl IntoIterator<Item = E::Fr>,
-        proof: &Self::Proof,
-        opening_challenge: E::Fr,
+        proof: &kzg10::Proof<E>,
+        opening_challenges: &dyn Fn(u64) -> E::Fr,
         _rng: Option<&mut dyn RngCore>,
-    ) -> Result<bool, Self::Error>
+    ) -> Result<bool, Error>
     where
-        Self::Commitment: 'a,
+        Commitment<E>: 'a,
     {
         let check_time = start_timer!(|| "Checking evaluations");
         let (combined_comm, combined_value) =
-            Self::accumulate_commitments_and_values(vk, commitments, values, opening_challenge)?;
+            Self::accumulate_commitments_and_values_individual_opening_challenges(
+                vk,
+                commitments,
+                values,
+                opening_challenges,
+            )?;
         let combined_comm = kzg10::Commitment(combined_comm.into());
         let result = kzg10::KZG10::check(&vk.vk, &combined_comm, point, combined_value, proof)?;
         end_timer!(check_time);
         Ok(result)
     }
 
-    fn batch_check<'a, R: RngCore>(
-        vk: &Self::VerifierKey,
-        commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
+    fn batch_check_individual_opening_challenges<'a, R: RngCore>(
+        vk: &VerifierKey<E>,
+        commitments: impl IntoIterator<Item = &'a LabeledCommitment<Commitment<E>>>,
         query_set: &QuerySet<E::Fr>,
-        values: &Evaluations<E::Fr>,
-        proof: &Self::BatchProof,
-        opening_challenge: E::Fr,
+        evaluations: &Evaluations<E::Fr>,
+        proof: &Vec<kzg10::Proof<E>>,
+        opening_challenges: &dyn Fn(u64) -> E::Fr,
         rng: &mut R,
-    ) -> Result<bool, Self::Error>
+    ) -> Result<bool, Error>
     where
-        Self::Commitment: 'a,
+        Commitment<E>: '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);
+        for (label, (point_label, point)) in query_set.iter() {
+            let labels = query_to_labels_map
+                .entry(point_label)
+                .or_insert((point, BTreeSet::new()));
+            labels.1.insert(label);
         }
         assert_eq!(proof.len(), query_to_labels_map.len());
 
         let mut combined_comms = Vec::new();
         let mut combined_queries = Vec::new();
         let mut combined_evals = Vec::new();
-        for (query, labels) in query_to_labels_map.into_iter() {
+        for (_, (point, labels)) in query_to_labels_map.into_iter() {
             let lc_time =
                 start_timer!(|| format!("Randomly combining {} commitments", labels.len()));
             let mut comms_to_combine: Vec<&'_ LabeledCommitment<_>> = Vec::new();
@@ -480,24 +502,27 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for MarlinKZG10<E> {
                     commitment.commitment().shifted_comm.is_some()
                 );
 
-                let v_i = values
-                    .get(&(label.clone(), *query))
-                    .ok_or(Error::MissingEvaluation {
-                        label: label.to_string(),
-                    })?;
+                let v_i =
+                    evaluations
+                        .get(&(label.clone(), *point))
+                        .ok_or(Error::MissingEvaluation {
+                            label: label.to_string(),
+                        })?;
 
                 comms_to_combine.push(commitment);
                 values_to_combine.push(*v_i);
             }
-            let (c, v) = Self::accumulate_commitments_and_values(
+
+            let (c, v) = Self::accumulate_commitments_and_values_individual_opening_challenges(
                 vk,
                 comms_to_combine,
                 values_to_combine,
-                opening_challenge,
+                opening_challenges,
             )?;
             end_timer!(lc_time);
+
             combined_comms.push(c);
-            combined_queries.push(*query);
+            combined_queries.push(*point);
             combined_evals.push(v);
         }
         let norm_time = start_timer!(|| "Normalizaing combined commitments");
@@ -520,19 +545,19 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for MarlinKZG10<E> {
         Ok(result)
     }
 
-    fn open_combinations<'a>(
-        ck: &Self::CommitterKey,
+    fn open_combinations_individual_opening_challenges<'a>(
+        ck: &CommitterKey<E>,
         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>>,
+        polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<E::Fr>>,
+        commitments: impl IntoIterator<Item = &'a LabeledCommitment<Commitment<E>>>,
         query_set: &QuerySet<E::Fr>,
-        opening_challenge: E::Fr,
-        rands: impl IntoIterator<Item = &'a Self::Randomness>,
+        opening_challenges: &dyn Fn(u64) -> E::Fr,
+        rands: impl IntoIterator<Item = &'a Randomness<E>>,
         rng: Option<&mut dyn RngCore>,
-    ) -> Result<BatchLCProof<E::Fr, Self>, Self::Error>
+    ) -> Result<BatchLCProof<E::Fr, Self>, Error>
     where
-        Self::Randomness: 'a,
-        Self::Commitment: 'a,
+        Randomness<E>: 'a,
+        Commitment<E>: 'a,
     {
         let label_map = polynomials
             .into_iter()
@@ -552,7 +577,7 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for MarlinKZG10<E> {
             let mut degree_bound = None;
             let mut hiding_bound = None;
 
-            let mut randomness = Self::Randomness::empty();
+            let mut randomness = Randomness::<E>::empty();
             assert!(randomness.shifted_rand.is_none());
 
             let mut coeffs_and_comms = Vec::new();
@@ -573,7 +598,7 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for MarlinKZG10<E> {
                     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));
+                    return Err(Error::EquationHasDegreeBounds(lc_label));
                 }
 
                 // Some(_) > None, always.
@@ -588,7 +613,7 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for MarlinKZG10<E> {
             }
 
             let lc_poly =
-                LabeledPolynomial::new_owned(lc_label.clone(), poly, degree_bound, hiding_bound);
+                LabeledPolynomial::new(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));
@@ -602,12 +627,12 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for MarlinKZG10<E> {
             .map(|((label, d), c)| LabeledCommitment::new(label, c, d))
             .collect::<Vec<_>>();
 
-        let proof = Self::batch_open(
+        let proof = Self::batch_open_individual_opening_challenges(
             ck,
             lc_polynomials.iter(),
             lc_commitments.iter(),
             &query_set,
-            opening_challenge,
+            opening_challenges,
             lc_randomness.iter(),
             rng,
         )?;
@@ -617,18 +642,18 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for MarlinKZG10<E> {
 
     /// 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,
+    fn check_combinations_individual_opening_challenges<'a, R: RngCore>(
+        vk: &VerifierKey<E>,
         lc_s: impl IntoIterator<Item = &'a LinearCombination<E::Fr>>,
-        commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
+        commitments: impl IntoIterator<Item = &'a LabeledCommitment<Commitment<E>>>,
         query_set: &QuerySet<E::Fr>,
         evaluations: &Evaluations<E::Fr>,
         proof: &BatchLCProof<E::Fr, Self>,
-        opening_challenge: E::Fr,
+        opening_challenges: &dyn Fn(u64) -> E::Fr,
         rng: &mut R,
-    ) -> Result<bool, Self::Error>
+    ) -> Result<bool, Error>
     where
-        Self::Commitment: 'a,
+        Commitment<E>: 'a,
     {
         let BatchLCProof { proof, .. } = proof;
         let label_comm_map = commitments
@@ -668,9 +693,9 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for MarlinKZG10<E> {
                         );
                         degree_bound = cur_comm.degree_bound();
                     } else if cur_comm.degree_bound().is_some() {
-                        return Err(Self::Error::EquationHasDegreeBounds(lc_label));
+                        return Err(Error::EquationHasDegreeBounds(lc_label));
                     }
-                    coeffs_and_comms.push((*coeff, cur_comm.commitment()));
+                    coeffs_and_comms.push((coeff.clone(), cur_comm.commitment()));
                 }
             }
             let lc_time =
@@ -689,16 +714,91 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for MarlinKZG10<E> {
             .collect::<Vec<_>>();
         end_timer!(combined_comms_norm_time);
 
-        Self::batch_check(
+        Self::batch_check_individual_opening_challenges(
             vk,
             &lc_commitments,
             &query_set,
             &evaluations,
             proof,
-            opening_challenge,
+            opening_challenges,
             rng,
         )
     }
+
+    // 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_individual_opening_challenges<'a>(
+        ck: &CommitterKey<E>,
+        labeled_polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<E::Fr>>,
+        commitments: impl IntoIterator<Item = &'a LabeledCommitment<Commitment<E>>>,
+        query_set: &QuerySet<E::Fr>,
+        opening_challenges: &dyn Fn(u64) -> E::Fr,
+        rands: impl IntoIterator<Item = &'a Randomness<E>>,
+        rng: Option<&mut dyn RngCore>,
+    ) -> Result<Vec<kzg10::Proof<E>>, Error>
+    where
+        Randomness<E>: 'a,
+        Commitment<E>: 'a,
+    {
+        let rng = &mut crate::optional_rng::OptionalRng(rng);
+        let poly_rand_comm: BTreeMap<_, _> = labeled_polynomials
+            .into_iter()
+            .zip(rands)
+            .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 {}",
+            poly_rand_comm.len(),
+            query_set.len(),
+        ));
+
+        let mut query_to_labels_map = BTreeMap::new();
+
+        for (label, (point_label, point)) in query_set.iter() {
+            let labels = query_to_labels_map
+                .entry(point_label)
+                .or_insert((point, BTreeSet::new()));
+            labels.1.insert(label);
+        }
+
+        let mut proofs = Vec::new();
+        for (_point_label, (point, labels)) in query_to_labels_map.into_iter() {
+            let mut query_polys: Vec<&'a LabeledPolynomial<_>> = Vec::new();
+            let mut query_rands: Vec<&'a Randomness<E>> = Vec::new();
+            let mut query_comms: Vec<&'a LabeledCommitment<Commitment<E>>> = Vec::new();
+
+            for label in labels {
+                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_individual_opening_challenges(
+                ck,
+                query_polys,
+                query_comms,
+                point.clone(),
+                opening_challenges,
+                query_rands,
+                Some(rng),
+            )?;
+
+            end_timer!(proof_time);
+
+            proofs.push(proof);
+        }
+        end_timer!(open_time);
+
+        Ok(proofs.into())
+    }
 }
 
 #[cfg(test)]
diff --git a/src/sonic_pc/data_structures.rs b/src/sonic_pc/data_structures.rs
index 6b5f3a4..41dacd2 100644
--- a/src/sonic_pc/data_structures.rs
+++ b/src/sonic_pc/data_structures.rs
@@ -1,6 +1,8 @@
 use crate::kzg10;
-use crate::{BTreeMap, PCCommitterKey, PCVerifierKey, Vec};
-use ark_ec::PairingEngine;
+use crate::{
+    BTreeMap, PCCommitterKey, PCPreparedCommitment, PCPreparedVerifierKey, PCVerifierKey, Vec,
+};
+use ark_ec::{PairingEngine, ProjectiveCurve};
 
 /// `UniversalParams` are the universal parameters for the KZG10 scheme.
 pub type UniversalParams<E> = kzg10::UniversalParams<E>;
@@ -11,6 +13,23 @@ pub type Randomness<E> = kzg10::Randomness<E>;
 /// `Commitment` is the commitment for the KZG10 scheme.
 pub type Commitment<E> = kzg10::Commitment<E>;
 
+/// `PreparedCommitment` is the prepared commitment for the KZG10 scheme.
+pub type PreparedCommitment<E> = kzg10::PreparedCommitment<E>;
+
+impl<E: PairingEngine> PCPreparedCommitment<Commitment<E>> for PreparedCommitment<E> {
+    /// prepare `PreparedCommitment` from `Commitment`
+    fn prepare(comm: &Commitment<E>) -> Self {
+        let mut prepared_comm = Vec::<E::G1Affine>::new();
+        let mut cur = E::G1Projective::from(comm.0.clone());
+        for _ in 0..128 {
+            prepared_comm.push(cur.clone().into());
+            cur.double_in_place();
+        }
+
+        Self { 0: prepared_comm }
+    }
+}
+
 /// `ComitterKey` is used to commit to, and create evaluation proofs for, a given
 /// polynomial.
 #[derive(Derivative)]
@@ -158,6 +177,16 @@ impl<E: PairingEngine> PCVerifierKey for VerifierKey<E> {
     }
 }
 
+/// Nothing to do to prepare this verifier key (for now).
+pub type PreparedVerifierKey<E> = VerifierKey<E>;
+
+impl<E: PairingEngine> PCPreparedVerifierKey<VerifierKey<E>> for PreparedVerifierKey<E> {
+    /// prepare `PreparedVerifierKey` from `VerifierKey`
+    fn prepare(vk: &VerifierKey<E>) -> Self {
+        vk.clone()
+    }
+}
+
 /// Evaluation proof at a query set.
 #[derive(Derivative)]
 #[derivative(
diff --git a/src/sonic_pc/mod.rs b/src/sonic_pc/mod.rs
index ed6b5b1..4cd9030 100644
--- a/src/sonic_pc/mod.rs
+++ b/src/sonic_pc/mod.rs
@@ -28,7 +28,7 @@ pub struct SonicKZG10<E: PairingEngine> {
 }
 
 impl<E: PairingEngine> SonicKZG10<E> {
-    fn accumulate_elems<'a>(
+    fn accumulate_elems_individual_opening_challenges<'a>(
         combined_comms: &mut BTreeMap<Option<usize>, E::G1Projective>,
         combined_witness: &mut E::G1Projective,
         combined_adjusted_witness: &mut E::G1Projective,
@@ -37,11 +37,14 @@ impl<E: PairingEngine> SonicKZG10<E> {
         point: E::Fr,
         values: impl IntoIterator<Item = E::Fr>,
         proof: &kzg10::Proof<E>,
-        opening_challenge: E::Fr,
+        opening_challenges: &dyn Fn(u64) -> E::Fr,
         randomizer: Option<E::Fr>,
     ) {
         let acc_time = start_timer!(|| "Accumulating elements");
-        let mut curr_challenge = opening_challenge;
+
+        let mut opening_challenge_counter = 0;
+        let mut curr_challenge = opening_challenges(opening_challenge_counter);
+        opening_challenge_counter += 1;
 
         // Keeps track of running combination of values
         let mut combined_values = E::Fr::zero();
@@ -64,7 +67,8 @@ impl<E: PairingEngine> SonicKZG10<E> {
             *combined_comms
                 .entry(degree_bound)
                 .or_insert(E::G1Projective::zero()) += &comm_with_challenge;
-            curr_challenge *= &opening_challenge;
+            curr_challenge = opening_challenges(opening_challenge_counter);
+            opening_challenge_counter += 1;
         }
 
         // Push expected results into list of elems. Power will be the negative of the expected power
@@ -129,7 +133,9 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for SonicKZG10<E> {
     type UniversalParams = UniversalParams<E>;
     type CommitterKey = CommitterKey<E>;
     type VerifierKey = VerifierKey<E>;
+    type PreparedVerifierKey = PreparedVerifierKey<E>;
     type Commitment = Commitment<E>;
+    type PreparedCommitment = PreparedCommitment<E>;
     type Randomness = Randomness<E>;
     type Proof = kzg10::Proof<E>;
     type BatchProof = Vec<Self::Proof>;
@@ -266,7 +272,7 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for SonicKZG10<E> {
     /// Outputs a commitment to `polynomial`.
     fn commit<'a>(
         ck: &Self::CommitterKey,
-        polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<'a, E::Fr>>,
+        polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<E::Fr>>,
         rng: Option<&mut dyn RngCore>,
     ) -> Result<
         (
@@ -327,12 +333,12 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for SonicKZG10<E> {
         Ok((labeled_comms, randomness))
     }
 
-    fn open<'a>(
+    fn open_individual_opening_challenges<'a>(
         ck: &Self::CommitterKey,
-        labeled_polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<'a, E::Fr>>,
+        labeled_polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<E::Fr>>,
         _commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
         point: E::Fr,
-        opening_challenge: E::Fr,
+        opening_challenges: &dyn Fn(u64) -> E::Fr,
         rands: impl IntoIterator<Item = &'a Self::Randomness>,
         _rng: Option<&mut dyn RngCore>,
     ) -> Result<Self::Proof, Self::Error>
@@ -342,7 +348,11 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for SonicKZG10<E> {
     {
         let mut combined_polynomial = Polynomial::zero();
         let mut combined_rand = kzg10::Randomness::empty();
-        let mut curr_challenge = opening_challenge;
+
+        let mut opening_challenge_counter = 0;
+
+        let mut curr_challenge = opening_challenges(opening_challenge_counter);
+        opening_challenge_counter += 1;
 
         for (polynomial, rand) in labeled_polynomials.into_iter().zip(rands) {
             let enforced_degree_bounds: Option<&[usize]> = ck
@@ -359,7 +369,8 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for SonicKZG10<E> {
 
             combined_polynomial += (curr_challenge, polynomial.polynomial());
             combined_rand += (curr_challenge, rand);
-            curr_challenge *= &opening_challenge;
+            curr_challenge = opening_challenges(opening_challenge_counter);
+            opening_challenge_counter += 1;
         }
 
         let proof_time = start_timer!(|| "Creating proof for polynomials");
@@ -369,13 +380,13 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for SonicKZG10<E> {
         Ok(proof)
     }
 
-    fn check<'a>(
+    fn check_individual_opening_challenges<'a>(
         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,
+        opening_challenges: &dyn Fn(u64) -> E::Fr,
         _rng: Option<&mut dyn RngCore>,
     ) -> Result<bool, Self::Error>
     where
@@ -386,7 +397,7 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for SonicKZG10<E> {
         let mut combined_witness: E::G1Projective = E::G1Projective::zero();
         let mut combined_adjusted_witness: E::G1Projective = E::G1Projective::zero();
 
-        Self::accumulate_elems(
+        Self::accumulate_elems_individual_opening_challenges(
             &mut combined_comms,
             &mut combined_witness,
             &mut combined_adjusted_witness,
@@ -395,7 +406,7 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for SonicKZG10<E> {
             point,
             values,
             proof,
-            opening_challenge,
+            opening_challenges,
             None,
         );
 
@@ -409,13 +420,13 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for SonicKZG10<E> {
         res
     }
 
-    fn batch_check<'a, R: RngCore>(
+    fn batch_check_individual_opening_challenges<'a, R: RngCore>(
         vk: &Self::VerifierKey,
         commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
         query_set: &QuerySet<E::Fr>,
         values: &Evaluations<E::Fr>,
         proof: &Self::BatchProof,
-        opening_challenge: E::Fr,
+        opening_challenges: &dyn Fn(u64) -> E::Fr,
         rng: &mut R,
     ) -> Result<bool, Self::Error>
     where
@@ -424,9 +435,11 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for SonicKZG10<E> {
         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);
+        for (label, (point_label, point)) in query_set.iter() {
+            let labels = query_to_labels_map
+                .entry(point_label)
+                .or_insert((point, BTreeSet::new()));
+            labels.1.insert(label);
         }
 
         assert_eq!(proof.len(), query_to_labels_map.len());
@@ -437,7 +450,7 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for SonicKZG10<E> {
         let mut combined_witness: E::G1Projective = E::G1Projective::zero();
         let mut combined_adjusted_witness: E::G1Projective = E::G1Projective::zero();
 
-        for ((query, labels), p) in query_to_labels_map.into_iter().zip(proof) {
+        for ((_point_label, (point, labels)), p) in query_to_labels_map.into_iter().zip(proof) {
             let mut comms_to_combine: Vec<&'_ LabeledCommitment<_>> = Vec::new();
             let mut values_to_combine = Vec::new();
             for label in labels.into_iter() {
@@ -446,7 +459,7 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for SonicKZG10<E> {
                 })?;
 
                 let v_i = values
-                    .get(&(label.clone(), *query))
+                    .get(&(label.clone(), *point))
                     .ok_or(Error::MissingEvaluation {
                         label: label.to_string(),
                     })?;
@@ -455,16 +468,16 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for SonicKZG10<E> {
                 values_to_combine.push(*v_i);
             }
 
-            Self::accumulate_elems(
+            Self::accumulate_elems_individual_opening_challenges(
                 &mut combined_comms,
                 &mut combined_witness,
                 &mut combined_adjusted_witness,
                 vk,
                 comms_to_combine.into_iter(),
-                *query,
+                *point,
                 values_to_combine.into_iter(),
                 p,
-                opening_challenge,
+                opening_challenges,
                 Some(randomizer),
             );
 
@@ -479,13 +492,13 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for SonicKZG10<E> {
         )
     }
 
-    fn open_combinations<'a>(
+    fn open_combinations_individual_opening_challenges<'a>(
         ck: &Self::CommitterKey,
         lc_s: impl IntoIterator<Item = &'a LinearCombination<E::Fr>>,
-        polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<'a, E::Fr>>,
+        polynomials: impl IntoIterator<Item = &'a LabeledPolynomial<E::Fr>>,
         commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
         query_set: &QuerySet<E::Fr>,
-        opening_challenge: E::Fr,
+        opening_challenges: &dyn Fn(u64) -> E::Fr,
         rands: impl IntoIterator<Item = &'a Self::Randomness>,
         rng: Option<&mut dyn RngCore>,
     ) -> Result<BatchLCProof<E::Fr, Self>, Self::Error>
@@ -540,7 +553,7 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for SonicKZG10<E> {
             }
 
             let lc_poly =
-                LabeledPolynomial::new_owned(lc_label.clone(), poly, degree_bound, hiding_bound);
+                LabeledPolynomial::new(lc_label.clone(), poly, degree_bound, hiding_bound);
             lc_polynomials.push(lc_poly);
             lc_randomness.push(randomness);
             lc_commitments.push(comm);
@@ -559,12 +572,12 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for SonicKZG10<E> {
             .map(|((label, d), c)| LabeledCommitment::new(label, c, d))
             .collect::<Vec<_>>();
 
-        let proof = Self::batch_open(
+        let proof = Self::batch_open_individual_opening_challenges(
             ck,
             lc_polynomials.iter(),
             lc_commitments.iter(),
             &query_set,
-            opening_challenge,
+            opening_challenges,
             lc_randomness.iter(),
             rng,
         )?;
@@ -573,14 +586,14 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for SonicKZG10<E> {
 
     /// Checks that `values` are the true evaluations at `query_set` of the polynomials
     /// committed in `labeled_commitments`.
-    fn check_combinations<'a, R: RngCore>(
+    fn check_combinations_individual_opening_challenges<'a, R: RngCore>(
         vk: &Self::VerifierKey,
         lc_s: impl IntoIterator<Item = &'a LinearCombination<E::Fr>>,
         commitments: impl IntoIterator<Item = &'a LabeledCommitment<Self::Commitment>>,
         query_set: &QuerySet<E::Fr>,
         evaluations: &Evaluations<E::Fr>,
         proof: &BatchLCProof<E::Fr, Self>,
-        opening_challenge: E::Fr,
+        opening_challenges: &dyn Fn(u64) -> E::Fr,
         rng: &mut R,
     ) -> Result<bool, Self::Error>
     where
@@ -644,13 +657,13 @@ impl<E: PairingEngine> PolynomialCommitment<E::Fr> for SonicKZG10<E> {
             .map(|((label, d), c)| LabeledCommitment::new(label, c, d))
             .collect::<Vec<_>>();
 
-        Self::batch_check(
+        Self::batch_check_individual_opening_challenges(
             vk,
             &lc_commitments,
             &query_set,
             &evaluations,
             proof,
-            opening_challenge,
+            opening_challenges,
             rng,
         )
     }
-- 
GitLab