arnaucube
arnaucube/sonobePublic Self-hosted repository
sonobe/folding-schemes/src/folding/hypernova/cccs.rs
folding-schemes/src/folding/hypernova/cccs.rsRaw
  1use ark_ec::CurveGroup;
  2use ark_ff::PrimeField;
  3use ark_std::One;
  4use ark_std::Zero;
  5use std::sync::Arc;
  6
  7use ark_std::rand::Rng;
  8
  9use super::Witness;
 10use crate::arith::{ccs::CCS, Arith};
 11use crate::commitment::CommitmentScheme;
 12use crate::utils::mle::dense_vec_to_dense_mle;
 13use crate::utils::vec::mat_vec_mul;
 14use crate::utils::virtual_polynomial::{build_eq_x_r_vec, VirtualPolynomial};
 15use crate::Error;
 16
 17/// Committed CCS instance
 18#[derive(Debug, Clone)]
 19pub struct CCCS<C: CurveGroup> {
 20    // Commitment to witness
 21    pub C: C,
 22    // Public input/output
 23    pub x: Vec<C::ScalarField>,
 24}
 25
 26impl<F: PrimeField> CCS<F> {
 27    pub fn to_cccs<R: Rng, C: CurveGroup, CS: CommitmentScheme<C>>(
 28        &self,
 29        rng: &mut R,
 30        cs_params: &CS::ProverParams,
 31        z: &[C::ScalarField],
 32    ) -> Result<(CCCS<C>, Witness<C::ScalarField>), Error>
 33    where
 34        // enforce that CCS's F is the C::ScalarField
 35        C: CurveGroup<ScalarField = F>,
 36    {
 37        let w: Vec<C::ScalarField> = z[(1 + self.l)..].to_vec();
 38
 39        // if the commitment scheme is set to be hiding, set the random blinding parameter
 40        let r_w = if CS::is_hiding() {
 41            C::ScalarField::rand(rng)
 42        } else {
 43            C::ScalarField::zero()
 44        };
 45        let C = CS::commit(cs_params, &w, &r_w)?;
 46
 47        Ok((
 48            CCCS::<C> {
 49                C,
 50                x: z[1..(1 + self.l)].to_vec(),
 51            },
 52            Witness::<C::ScalarField> { w, r_w },
 53        ))
 54    }
 55
 56    /// Computes q(x) = \sum^q c_i * \prod_{j \in S_i} ( \sum_{y \in {0,1}^s'} M_j(x, y) * z(y) )
 57    /// polynomial over x
 58    pub fn compute_q(&self, z: &[F]) -> Result<VirtualPolynomial<F>, Error> {
 59        let mut q_x = VirtualPolynomial::<F>::new(self.s);
 60        for i in 0..self.q {
 61            let mut Q_k = vec![];
 62            for &j in self.S[i].iter() {
 63                Q_k.push(dense_vec_to_dense_mle(self.s, &mat_vec_mul(&self.M[j], z)?));
 64            }
 65            q_x.add_mle_list(Q_k.iter().map(|v| Arc::new(v.clone())), self.c[i])?;
 66        }
 67        Ok(q_x)
 68    }
 69
 70    /// Computes Q(x) = eq(beta, x) * q(x)
 71    ///               = eq(beta, x) * \sum^q c_i * \prod_{j \in S_i} ( \sum_{y \in {0,1}^s'} M_j(x, y) * z(y) )
 72    /// polynomial over x
 73    pub fn compute_Q(&self, z: &[F], beta: &[F]) -> Result<VirtualPolynomial<F>, Error> {
 74        let eq_beta = build_eq_x_r_vec(beta)?;
 75        let eq_beta_mle = dense_vec_to_dense_mle(self.s, &eq_beta);
 76
 77        let mut Q = VirtualPolynomial::<F>::new(self.s);
 78        for i in 0..self.q {
 79            let mut Q_k = vec![];
 80            for &j in self.S[i].iter() {
 81                Q_k.push(dense_vec_to_dense_mle(self.s, &mat_vec_mul(&self.M[j], z)?));
 82            }
 83            Q_k.push(eq_beta_mle.clone());
 84            Q.add_mle_list(Q_k.iter().map(|v| Arc::new(v.clone())), self.c[i])?;
 85        }
 86        Ok(Q)
 87    }
 88}
 89
 90impl<C: CurveGroup> CCCS<C> {
 91    pub fn dummy(l: usize) -> CCCS<C>
 92    where
 93        C::ScalarField: PrimeField,
 94    {
 95        CCCS::<C> {
 96            C: C::zero(),
 97            x: vec![C::ScalarField::zero(); l],
 98        }
 99    }
100
101    /// Perform the check of the CCCS instance described at section 4.1,
102    /// notice that this method does not check the commitment correctness
103    pub fn check_relation(
104        &self,
105        ccs: &CCS<C::ScalarField>,
106        w: &Witness<C::ScalarField>,
107    ) -> Result<(), Error> {
108        // check CCCS relation
109        let z: Vec<C::ScalarField> =
110            [vec![C::ScalarField::one()], self.x.clone(), w.w.to_vec()].concat();
111
112        // A CCCS relation is satisfied if the q(x) multivariate polynomial evaluates to zero in
113        // the hypercube, evaluating over the whole boolean hypercube for a normal-sized instance
114        // would take too much, this checks the CCS relation of the CCCS.
115        ccs.check_relation(&z)?;
116
117        Ok(())
118    }
119}
120
121#[cfg(test)]
122pub mod tests {
123    use ark_pallas::Fr;
124    use ark_std::test_rng;
125    use ark_std::UniformRand;
126
127    use super::*;
128    use crate::arith::ccs::tests::{get_test_ccs, get_test_z};
129    use crate::utils::hypercube::BooleanHypercube;
130
131    /// Do some sanity checks on q(x). It's a multivariable polynomial and it should evaluate to zero inside the
132    /// hypercube, but to not-zero outside the hypercube.
133    #[test]
134    fn test_compute_q() {
135        let mut rng = test_rng();
136
137        let ccs = get_test_ccs::<Fr>();
138        let z = get_test_z(3);
139
140        let q = ccs.compute_q(&z).unwrap();
141
142        // Evaluate inside the hypercube
143        for x in BooleanHypercube::new(ccs.s) {
144            assert_eq!(Fr::zero(), q.evaluate(&x).unwrap());
145        }
146
147        // Evaluate outside the hypercube
148        let beta: Vec<Fr> = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect();
149        assert_ne!(Fr::zero(), q.evaluate(&beta).unwrap());
150    }
151
152    /// Perform some sanity checks on Q(x).
153    #[test]
154    fn test_compute_Q() {
155        let mut rng = test_rng();
156
157        let ccs: CCS<Fr> = get_test_ccs();
158        let z = get_test_z(3);
159        ccs.check_relation(&z).unwrap();
160
161        let beta: Vec<Fr> = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect();
162
163        // Compute Q(x) = eq(beta, x) * q(x).
164        let Q = ccs.compute_Q(&z, &beta).unwrap();
165
166        // Let's consider the multilinear polynomial G(x) = \sum_{y \in {0, 1}^s} eq(x, y) q(y)
167        // which interpolates the multivariate polynomial q(x) inside the hypercube.
168        //
169        // Observe that summing Q(x) inside the hypercube, directly computes G(\beta).
170        //
171        // Now, G(x) is multilinear and agrees with q(x) inside the hypercube. Since q(x) vanishes inside the
172        // hypercube, this means that G(x) also vanishes in the hypercube. Since G(x) is multilinear and vanishes
173        // inside the hypercube, this makes it the zero polynomial.
174        //
175        // Hence, evaluating G(x) at a random beta should give zero.
176
177        // Now sum Q(x) evaluations in the hypercube and expect it to be 0
178        let r = BooleanHypercube::new(ccs.s)
179            .map(|x| Q.evaluate(&x).unwrap())
180            .fold(Fr::zero(), |acc, result| acc + result);
181        assert_eq!(r, Fr::zero());
182    }
183
184    /// The polynomial G(x) (see above) interpolates q(x) inside the hypercube.
185    /// Summing Q(x) over the hypercube is equivalent to evaluating G(x) at some point.
186    /// This test makes sure that G(x) agrees with q(x) inside the hypercube, but not outside
187    #[test]
188    fn test_Q_against_q() {
189        let mut rng = test_rng();
190
191        let ccs: CCS<Fr> = get_test_ccs();
192        let z = get_test_z(3);
193        ccs.check_relation(&z).unwrap();
194
195        // Now test that if we create Q(x) with eq(d,y) where d is inside the hypercube, \sum Q(x) should be G(d) which
196        // should be equal to q(d), since G(x) interpolates q(x) inside the hypercube
197        let q = ccs.compute_q(&z).unwrap();
198        for d in BooleanHypercube::new(ccs.s) {
199            let Q_at_d = ccs.compute_Q(&z, &d).unwrap();
200
201            // Get G(d) by summing over Q_d(x) over the hypercube
202            let G_at_d = BooleanHypercube::new(ccs.s)
203                .map(|x| Q_at_d.evaluate(&x).unwrap())
204                .fold(Fr::zero(), |acc, result| acc + result);
205            assert_eq!(G_at_d, q.evaluate(&d).unwrap());
206        }
207
208        // Now test that they should disagree outside of the hypercube
209        let r: Vec<Fr> = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect();
210        let Q_at_r = ccs.compute_Q(&z, &r).unwrap();
211
212        // Get G(d) by summing over Q_d(x) over the hypercube
213        let G_at_r = BooleanHypercube::new(ccs.s)
214            .map(|x| Q_at_r.evaluate(&x).unwrap())
215            .fold(Fr::zero(), |acc, result| acc + result);
216        assert_ne!(G_at_r, q.evaluate(&r).unwrap());
217    }
218}