arnaucube
arnaucube/sonobePublic Self-hosted repository
sonobe/folding-schemes/src/folding/nova/nifs/ova.rs
folding-schemes/src/folding/nova/nifs/ova.rsRaw
  1/// This module contains the implementation the NIFSTrait for the
  2/// [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw) NIFS (Non-Interactive Folding Scheme).
  3use ark_crypto_primitives::sponge::Absorb;
  4use ark_ec::{CurveGroup, Group};
  5use ark_ff::{BigInteger, PrimeField};
  6use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
  7use ark_std::fmt::Debug;
  8use ark_std::rand::RngCore;
  9use ark_std::{One, UniformRand, Zero};
 10use std::marker::PhantomData;
 11
 12use super::NIFSTrait;
 13use crate::arith::r1cs::R1CS;
 14use crate::commitment::CommitmentScheme;
 15use crate::folding::nova::circuits::ChallengeGadget;
 16use crate::folding::{circuits::CF1, traits::Dummy};
 17use crate::transcript::{AbsorbNonNative, Transcript};
 18use crate::utils::vec::{hadamard, mat_vec_mul, vec_scalar_mul, vec_sub};
 19use crate::Error;
 20
 21/// A CommittedInstance in [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw) is represented by `W` or
 22/// `W'`. It is the result of the commitment to a vector that contains the witness `w` concatenated
 23/// with `t` or `e` + the public inputs `x` and a relaxation factor `u`. (Notice that in the Ova
 24/// document `u` is denoted as `mu`, in this implementation we use `u` so it follows the original
 25/// Nova notation, so code is easier to follow).
 26#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
 27pub struct CommittedInstance<C: CurveGroup> {
 28    pub u: C::ScalarField, // in the Ova document is denoted as `mu`
 29    pub x: Vec<C::ScalarField>,
 30    pub cmWE: C,
 31}
 32
 33impl<C: CurveGroup> Absorb for CommittedInstance<C>
 34where
 35    C::ScalarField: Absorb,
 36{
 37    fn to_sponge_bytes(&self, dest: &mut Vec<u8>) {
 38        C::ScalarField::batch_to_sponge_bytes(&self.to_sponge_field_elements_as_vec(), dest);
 39    }
 40
 41    fn to_sponge_field_elements<F: PrimeField>(&self, dest: &mut Vec<F>) {
 42        self.u.to_sponge_field_elements(dest);
 43        self.x.to_sponge_field_elements(dest);
 44        // We cannot call `to_native_sponge_field_elements(dest)` directly, as
 45        // `to_native_sponge_field_elements` needs `F` to be `C::ScalarField`,
 46        // but here `F` is a generic `PrimeField`.
 47        self.cmWE
 48            .to_native_sponge_field_elements_as_vec()
 49            .to_sponge_field_elements(dest);
 50    }
 51}
 52
 53/// A Witness in Ova is represented by `w`. It also contains a blinder which can or not be used
 54/// when committing to the witness itself.
 55#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
 56pub struct Witness<C: CurveGroup> {
 57    pub w: Vec<C::ScalarField>,
 58    pub rW: C::ScalarField,
 59}
 60
 61impl<C: CurveGroup> Witness<C> {
 62    /// Generates a new `Witness` instance from a given witness vector.
 63    /// If `H = true`, then we assume we want to blind it at commitment time,
 64    /// hence sampling `rW` from the randomness passed.
 65    pub fn new<const H: bool>(w: Vec<C::ScalarField>, mut rng: impl RngCore) -> Self {
 66        Self {
 67            w,
 68            rW: if H {
 69                C::ScalarField::rand(&mut rng)
 70            } else {
 71                C::ScalarField::zero()
 72            },
 73        }
 74    }
 75
 76    /// Given `x` (public inputs) and `t` or `e` (which we always concatenate in Ova) and the
 77    /// public inputs `x`, generates a [`CommittedInstance`] as a result which will or not be
 78    /// blinded depending on how the const generic `HC` is set up.
 79    pub fn commit<CS: CommitmentScheme<C, HC>, const HC: bool>(
 80        &self,
 81        params: &CS::ProverParams,
 82        x: Vec<C::ScalarField>,
 83        t_or_e: Vec<C::ScalarField>,
 84    ) -> Result<CommittedInstance<C>, Error> {
 85        let cmWE = CS::commit(params, &[self.w.clone(), t_or_e].concat(), &self.rW)?;
 86        Ok(CommittedInstance {
 87            u: C::ScalarField::one(),
 88            cmWE,
 89            x,
 90        })
 91    }
 92}
 93
 94impl<C: CurveGroup> Dummy<&R1CS<CF1<C>>> for Witness<C> {
 95    fn dummy(r1cs: &R1CS<CF1<C>>) -> Self {
 96        Self {
 97            w: vec![C::ScalarField::zero(); r1cs.A.n_cols - 1 - r1cs.l],
 98            rW: C::ScalarField::zero(),
 99        }
100    }
101}
102
103/// Implements the NIFS (Non-Interactive Folding Scheme) trait for Ova.
104pub struct NIFS<
105    C: CurveGroup,
106    CS: CommitmentScheme<C, H>,
107    T: Transcript<C::ScalarField>,
108    const H: bool = false,
109> {
110    _c: PhantomData<C>,
111    _cp: PhantomData<CS>,
112    _t: PhantomData<T>,
113}
114
115impl<C: CurveGroup, CS: CommitmentScheme<C, H>, T: Transcript<C::ScalarField>, const H: bool>
116    NIFSTrait<C, CS, T, H> for NIFS<C, CS, T, H>
117where
118    <C as Group>::ScalarField: Absorb,
119    <C as CurveGroup>::BaseField: PrimeField,
120{
121    type CommittedInstance = CommittedInstance<C>;
122    type Witness = Witness<C>;
123    type ProverAux = ();
124    type Proof = ();
125
126    fn new_witness(w: Vec<C::ScalarField>, _e_len: usize, rng: impl RngCore) -> Self::Witness {
127        Witness::new::<H>(w, rng)
128    }
129
130    fn new_instance(
131        _rng: impl RngCore,
132        params: &CS::ProverParams,
133        W: &Self::Witness,
134        x: Vec<C::ScalarField>,
135        aux: Vec<C::ScalarField>, // t_or_e
136    ) -> Result<Self::CommittedInstance, Error> {
137        W.commit::<CS, H>(params, x, aux)
138    }
139
140    fn fold_witness(
141        r: C::ScalarField, // in Ova's hackmd denoted as `alpha`
142        W_i: &Self::Witness,
143        w_i: &Self::Witness,
144        _aux: &Self::ProverAux,
145    ) -> Result<Self::Witness, Error> {
146        let w: Vec<C::ScalarField> = W_i
147            .w
148            .iter()
149            .zip(&w_i.w)
150            .map(|(a, b)| *a + (r * b))
151            .collect();
152
153        let rW = W_i.rW + r * w_i.rW;
154        Ok(Self::Witness { w, rW })
155    }
156
157    fn prove(
158        _cs_prover_params: &CS::ProverParams,
159        _r1cs: &R1CS<C::ScalarField>,
160        transcript: &mut T,
161        pp_hash: C::ScalarField,
162        W_i: &Self::Witness,
163        U_i: &Self::CommittedInstance,
164        w_i: &Self::Witness,
165        u_i: &Self::CommittedInstance,
166    ) -> Result<
167        (
168            Self::Witness,
169            Self::CommittedInstance,
170            Self::Proof,
171            Vec<bool>,
172        ),
173        Error,
174    > {
175        let mut transcript_v = transcript.clone();
176
177        let r_bits = ChallengeGadget::<C, Self::CommittedInstance>::get_challenge_native(
178            transcript, pp_hash, U_i, u_i, None, // cmT not used in Ova
179        );
180        let r_Fr = C::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits))
181            .ok_or(Error::OutOfBounds)?;
182
183        let w = Self::fold_witness(r_Fr, W_i, w_i, &())?;
184
185        let (ci, _r_bits_v) = Self::verify(&mut transcript_v, pp_hash, U_i, u_i, &())?;
186        #[cfg(test)]
187        assert_eq!(_r_bits_v, r_bits);
188
189        Ok((w, ci, (), r_bits))
190    }
191
192    fn verify(
193        transcript: &mut T,
194        pp_hash: C::ScalarField,
195        U_i: &Self::CommittedInstance,
196        u_i: &Self::CommittedInstance,
197        _proof: &Self::Proof, // unused in Ova
198    ) -> Result<(Self::CommittedInstance, Vec<bool>), Error> {
199        let r_bits = ChallengeGadget::<C, Self::CommittedInstance>::get_challenge_native(
200            transcript, pp_hash, U_i, u_i, None, // cmT not used in Ova
201        );
202        let r = C::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits))
203            .ok_or(Error::OutOfBounds)?;
204
205        // recall that r=alpha, and u=mu between Nova and Ova respectively
206        let u = U_i.u + r; // u_i.u is always 1 IN ova as we just can do sequential IVC.
207        let cmWE = U_i.cmWE + u_i.cmWE.mul(r);
208        let x = U_i
209            .x
210            .iter()
211            .zip(&u_i.x)
212            .map(|(a, b)| *a + (r * b))
213            .collect::<Vec<C::ScalarField>>();
214
215        Ok((Self::CommittedInstance { cmWE, u, x }, r_bits))
216    }
217}
218
219/// Computes the E parameter (error terms) for the given R1CS and the instance's z and u. This
220/// method is used by the verifier to obtain E in order to check the RelaxedR1CS relation.
221pub fn compute_E<C: CurveGroup>(
222    r1cs: &R1CS<C::ScalarField>,
223    z: &[C::ScalarField],
224    u: C::ScalarField,
225) -> Result<Vec<C::ScalarField>, Error> {
226    let (A, B, C) = (r1cs.A.clone(), r1cs.B.clone(), r1cs.C.clone());
227
228    // this is parallelizable (for the future)
229    let Az = mat_vec_mul(&A, z)?;
230    let Bz = mat_vec_mul(&B, z)?;
231    let Cz = mat_vec_mul(&C, z)?;
232
233    let Az_Bz = hadamard(&Az, &Bz)?;
234    let uCz = vec_scalar_mul(&Cz, &u);
235
236    vec_sub(&Az_Bz, &uCz)
237}
238
239#[cfg(test)]
240pub mod tests {
241    use super::*;
242    use ark_pallas::{Fr, Projective};
243
244    use crate::arith::{r1cs::tests::get_test_r1cs, Arith};
245    use crate::commitment::pedersen::Pedersen;
246    use crate::folding::nova::nifs::tests::test_nifs_opt;
247    use ark_crypto_primitives::sponge::poseidon::PoseidonSponge;
248
249    // Simple auxiliary structure mainly used to help pass a witness for which we can check
250    // easily an R1CS relation.
251    // Notice that checking it requires us to have `E` as per [`Arith`] trait definition.
252    // But since we don't hold `E` nor `e` within the NIFS, we create this structure to pass
253    // `e` such that the check can be done.
254    #[derive(Debug, Clone)]
255    pub(crate) struct TestingWitness<C: CurveGroup> {
256        pub(crate) w: Vec<C::ScalarField>,
257        pub(crate) e: Vec<C::ScalarField>,
258    }
259    impl<C: CurveGroup> Arith<TestingWitness<C>, CommittedInstance<C>> for R1CS<CF1<C>> {
260        type Evaluation = Vec<CF1<C>>;
261
262        fn eval_relation(
263            &self,
264            w: &TestingWitness<C>,
265            u: &CommittedInstance<C>,
266        ) -> Result<Self::Evaluation, Error> {
267            self.eval_at_z(&[&[u.u], u.x.as_slice(), &w.w].concat())
268        }
269
270        fn check_evaluation(
271            w: &TestingWitness<C>,
272            _u: &CommittedInstance<C>,
273            e: Self::Evaluation,
274        ) -> Result<(), Error> {
275            (w.e == e).then_some(()).ok_or(Error::NotSatisfied)
276        }
277    }
278
279    #[test]
280    fn test_nifs_ova() {
281        let (W, U) = test_nifs_opt::<NIFS<Projective, Pedersen<Projective>, PoseidonSponge<Fr>>>();
282
283        // check the last folded instance relation
284        let r1cs = get_test_r1cs();
285        let z: Vec<Fr> = [&[U.u][..], &U.x, &W.w].concat();
286        let e = compute_E::<Projective>(&r1cs, &z, U.u).unwrap();
287        r1cs.check_relation(&TestingWitness::<Projective> { e, w: W.w.clone() }, &U)
288            .unwrap();
289    }
290}