arnaucube
arnaucube/sonobePublic Self-hosted repository
sonobe/folding-schemes/src/folding/hypernova/lcccs.rs
folding-schemes/src/folding/hypernova/lcccs.rsRaw
  1use ark_crypto_primitives::sponge::Absorb;
  2use ark_ec::{CurveGroup, Group};
  3use ark_ff::PrimeField;
  4use ark_poly::DenseMultilinearExtension;
  5use ark_poly::MultilinearExtension;
  6use ark_serialize::CanonicalDeserialize;
  7use ark_serialize::CanonicalSerialize;
  8use ark_std::rand::Rng;
  9use ark_std::Zero;
 10
 11use super::Witness;
 12use crate::arith::ccs::CCS;
 13use crate::commitment::CommitmentScheme;
 14use crate::transcript::{AbsorbNonNative, Transcript};
 15use crate::utils::mle::dense_vec_to_dense_mle;
 16use crate::utils::vec::mat_vec_mul;
 17use crate::Error;
 18
 19/// Linearized Committed CCS instance
 20#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
 21pub struct LCCCS<C: CurveGroup> {
 22    // Commitment to witness
 23    pub C: C,
 24    // Relaxation factor of z for folded LCCCS
 25    pub u: C::ScalarField,
 26    // Public input/output
 27    pub x: Vec<C::ScalarField>,
 28    // Random evaluation point for the v_i
 29    pub r_x: Vec<C::ScalarField>,
 30    // Vector of v_i
 31    pub v: Vec<C::ScalarField>,
 32}
 33
 34impl<F: PrimeField> CCS<F> {
 35    pub fn to_lcccs<R: Rng, C, CS: CommitmentScheme<C, H>, const H: bool>(
 36        &self,
 37        rng: &mut R,
 38        cs_params: &CS::ProverParams,
 39        z: &[C::ScalarField],
 40    ) -> Result<(LCCCS<C>, Witness<C::ScalarField>), Error>
 41    where
 42        // enforce that CCS's F is the C::ScalarField
 43        C: CurveGroup<ScalarField = F>,
 44    {
 45        let w: Vec<C::ScalarField> = z[(1 + self.l)..].to_vec();
 46        // if the commitment scheme is set to be hiding, set the random blinding parameter
 47        let r_w = if CS::is_hiding() {
 48            C::ScalarField::rand(rng)
 49        } else {
 50            C::ScalarField::zero()
 51        };
 52        let C = CS::commit(cs_params, &w, &r_w)?;
 53
 54        let r_x: Vec<C::ScalarField> = (0..self.s).map(|_| C::ScalarField::rand(rng)).collect();
 55
 56        let Mzs: Vec<DenseMultilinearExtension<F>> = self
 57            .M
 58            .iter()
 59            .map(|M_j| Ok(dense_vec_to_dense_mle(self.s, &mat_vec_mul(M_j, z)?)))
 60            .collect::<Result<_, Error>>()?;
 61
 62        // compute v_j
 63        let v: Vec<F> = Mzs
 64            .iter()
 65            .map(|Mz| Mz.evaluate(&r_x).ok_or(Error::EvaluationFail))
 66            .collect::<Result<_, Error>>()?;
 67
 68        Ok((
 69            LCCCS::<C> {
 70                C,
 71                u: C::ScalarField::one(),
 72                x: z[1..(1 + self.l)].to_vec(),
 73                r_x,
 74                v,
 75            },
 76            Witness::<C::ScalarField> { w, r_w },
 77        ))
 78    }
 79}
 80
 81impl<C: CurveGroup> LCCCS<C> {
 82    pub fn dummy(l: usize, t: usize, s: usize) -> LCCCS<C>
 83    where
 84        C::ScalarField: PrimeField,
 85    {
 86        LCCCS::<C> {
 87            C: C::zero(),
 88            u: C::ScalarField::zero(),
 89            x: vec![C::ScalarField::zero(); l],
 90            r_x: vec![C::ScalarField::zero(); s],
 91            v: vec![C::ScalarField::zero(); t],
 92        }
 93    }
 94
 95    /// Perform the check of the LCCCS instance described at section 4.2,
 96    /// notice that this method does not check the commitment correctness
 97    pub fn check_relation(
 98        &self,
 99        ccs: &CCS<C::ScalarField>,
100        w: &Witness<C::ScalarField>,
101    ) -> Result<(), Error> {
102        // check CCS relation
103        let z: Vec<C::ScalarField> = [vec![self.u], self.x.clone(), w.w.to_vec()].concat();
104
105        let computed_v: Vec<C::ScalarField> = ccs
106            .M
107            .iter()
108            .map(|M_j| {
109                let Mz_mle = dense_vec_to_dense_mle(ccs.s, &mat_vec_mul(M_j, &z)?);
110                Mz_mle.evaluate(&self.r_x).ok_or(Error::EvaluationFail)
111            })
112            .collect::<Result<_, Error>>()?;
113        if computed_v != self.v {
114            return Err(Error::NotSatisfied);
115        }
116        Ok(())
117    }
118}
119
120impl<C: CurveGroup> Absorb for LCCCS<C>
121where
122    C::ScalarField: Absorb,
123{
124    fn to_sponge_bytes(&self, _dest: &mut Vec<u8>) {
125        // This is never called
126        unimplemented!()
127    }
128
129    fn to_sponge_field_elements<F: PrimeField>(&self, dest: &mut Vec<F>) {
130        // We cannot call `to_native_sponge_field_elements(dest)` directly, as
131        // `to_native_sponge_field_elements` needs `F` to be `C::ScalarField`,
132        // but here `F` is a generic `PrimeField`.
133        self.C
134            .to_native_sponge_field_elements_as_vec()
135            .to_sponge_field_elements(dest);
136        self.u.to_sponge_field_elements(dest);
137        self.x.to_sponge_field_elements(dest);
138        self.r_x.to_sponge_field_elements(dest);
139        self.v.to_sponge_field_elements(dest);
140    }
141}
142
143impl<C: CurveGroup> LCCCS<C>
144where
145    <C as Group>::ScalarField: Absorb,
146    <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
147{
148    /// [`LCCCS`].hash implements the committed instance hash compatible with the gadget
149    /// implemented in nova/circuits.rs::CommittedInstanceVar.hash.
150    /// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and `U_i` is the LCCCS.
151    pub fn hash<T: Transcript<C::ScalarField>>(
152        &self,
153        sponge: &T,
154        pp_hash: C::ScalarField,
155        i: C::ScalarField,
156        z_0: Vec<C::ScalarField>,
157        z_i: Vec<C::ScalarField>,
158    ) -> C::ScalarField {
159        let mut sponge = sponge.clone();
160        sponge.absorb(&pp_hash);
161        sponge.absorb(&i);
162        sponge.absorb(&z_0);
163        sponge.absorb(&z_i);
164        sponge.absorb(&self);
165        sponge.squeeze_field_elements(1)[0]
166    }
167}
168
169#[cfg(test)]
170pub mod tests {
171    use ark_pallas::{Fr, Projective};
172    use ark_std::test_rng;
173    use ark_std::One;
174    use ark_std::UniformRand;
175    use std::sync::Arc;
176
177    use super::*;
178    use crate::arith::{
179        ccs::tests::{get_test_ccs, get_test_z},
180        r1cs::R1CS,
181        Arith,
182    };
183    use crate::commitment::pedersen::Pedersen;
184    use crate::utils::hypercube::BooleanHypercube;
185    use crate::utils::virtual_polynomial::{build_eq_x_r_vec, VirtualPolynomial};
186
187    // method for testing
188    pub fn compute_Ls<C: CurveGroup>(
189        ccs: &CCS<C::ScalarField>,
190        lcccs: &LCCCS<C>,
191        z: &[C::ScalarField],
192    ) -> Vec<VirtualPolynomial<C::ScalarField>> {
193        let eq_rx = build_eq_x_r_vec(&lcccs.r_x).unwrap();
194        let eq_rx_mle = dense_vec_to_dense_mle(ccs.s, &eq_rx);
195
196        let mut Ls = Vec::with_capacity(ccs.t);
197        for M_j in ccs.M.iter() {
198            let mut L = VirtualPolynomial::<C::ScalarField>::new(ccs.s);
199            let mut Mz = vec![dense_vec_to_dense_mle(ccs.s, &mat_vec_mul(M_j, z).unwrap())];
200            Mz.push(eq_rx_mle.clone());
201            L.add_mle_list(
202                Mz.iter().map(|v| Arc::new(v.clone())),
203                C::ScalarField::one(),
204            )
205            .unwrap();
206            Ls.push(L);
207        }
208        Ls
209    }
210
211    #[test]
212    /// Test linearized CCCS v_j against the L_j(x)
213    fn test_lcccs_v_j() {
214        let mut rng = test_rng();
215
216        let n_rows = 2_u32.pow(5) as usize;
217        let n_cols = 2_u32.pow(5) as usize;
218        let r1cs = R1CS::<Fr>::rand(&mut rng, n_rows, n_cols);
219        let ccs = CCS::from_r1cs(r1cs);
220        let z: Vec<Fr> = (0..n_cols).map(|_| Fr::rand(&mut rng)).collect();
221
222        let (pedersen_params, _) =
223            Pedersen::<Projective>::setup(&mut rng, ccs.n - ccs.l - 1).unwrap();
224
225        let (lcccs, _) = ccs
226            .to_lcccs::<_, Projective, Pedersen<Projective, false>, false>(
227                &mut rng,
228                &pedersen_params,
229                &z,
230            )
231            .unwrap();
232        // with our test vector coming from R1CS, v should have length 3
233        assert_eq!(lcccs.v.len(), 3);
234
235        let vec_L_j_x = compute_Ls(&ccs, &lcccs, &z);
236        assert_eq!(vec_L_j_x.len(), lcccs.v.len());
237
238        for (v_i, L_j_x) in lcccs.v.into_iter().zip(vec_L_j_x) {
239            let sum_L_j_x = BooleanHypercube::new(ccs.s)
240                .map(|y| L_j_x.evaluate(&y).unwrap())
241                .fold(Fr::zero(), |acc, result| acc + result);
242            assert_eq!(v_i, sum_L_j_x);
243        }
244    }
245
246    /// Given a bad z, check that the v_j should not match with the L_j(x)
247    #[test]
248    fn test_bad_v_j() {
249        let mut rng = test_rng();
250
251        let ccs = get_test_ccs();
252        let z = get_test_z(3);
253        ccs.check_relation(&z.clone()).unwrap();
254
255        // Mutate z so that the relation does not hold
256        let mut bad_z = z.clone();
257        bad_z[3] = Fr::zero();
258        assert!(ccs.check_relation(&bad_z.clone()).is_err());
259
260        let (pedersen_params, _) =
261            Pedersen::<Projective>::setup(&mut rng, ccs.n - ccs.l - 1).unwrap();
262        // Compute v_j with the right z
263        let (lcccs, _) = ccs
264            .to_lcccs::<_, Projective, Pedersen<Projective>, false>(&mut rng, &pedersen_params, &z)
265            .unwrap();
266        // with our test vector coming from R1CS, v should have length 3
267        assert_eq!(lcccs.v.len(), 3);
268
269        // Bad compute L_j(x) with the bad z
270        let vec_L_j_x = compute_Ls(&ccs, &lcccs, &bad_z);
271        assert_eq!(vec_L_j_x.len(), lcccs.v.len());
272
273        // Make sure that the LCCCS is not satisfied given these L_j(x)
274        // i.e. summing L_j(x) over the hypercube should not give v_j for all j
275        let mut satisfied = true;
276        for (v_i, L_j_x) in lcccs.v.into_iter().zip(vec_L_j_x) {
277            let sum_L_j_x = BooleanHypercube::new(ccs.s)
278                .map(|y| L_j_x.evaluate(&y).unwrap())
279                .fold(Fr::zero(), |acc, result| acc + result);
280            if v_i != sum_L_j_x {
281                satisfied = false;
282            }
283        }
284        assert!(!satisfied);
285    }
286}