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