/// This module contains the implementation the NIFSTrait for the /// [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw) NIFS (Non-Interactive Folding Scheme). use ark_crypto_primitives::sponge::Absorb; use ark_ec::{CurveGroup, Group}; use ark_ff::{BigInteger, PrimeField}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::fmt::Debug; use ark_std::rand::RngCore; use ark_std::{One, UniformRand, Zero}; use std::marker::PhantomData; use super::nova::ChallengeGadget; use super::ova_circuits::CommittedInstanceVar; use super::NIFSTrait; use crate::arith::r1cs::R1CS; use crate::commitment::CommitmentScheme; use crate::folding::traits::{CommittedInstanceOps, Inputize}; use crate::folding::{circuits::CF1, traits::Dummy}; use crate::transcript::{AbsorbNonNative, Transcript}; use crate::utils::vec::{hadamard, mat_vec_mul, vec_scalar_mul, vec_sub}; use crate::Error; /// A CommittedInstance in [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw) is represented by `W` or /// `W'`. It is the result of the commitment to a vector that contains the witness `w` concatenated /// with `t` or `e` + the public inputs `x` and a relaxation factor `u`. (Notice that in the Ova /// document `u` is denoted as `mu`, in this implementation we use `u` so it follows the original /// Nova notation, so code is easier to follow). #[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] pub struct CommittedInstance { pub u: C::ScalarField, // in the Ova document is denoted as `mu` pub x: Vec, pub cmWE: C, } impl Absorb for CommittedInstance where C::ScalarField: Absorb, { fn to_sponge_bytes(&self, dest: &mut Vec) { C::ScalarField::batch_to_sponge_bytes(&self.to_sponge_field_elements_as_vec(), dest); } fn to_sponge_field_elements(&self, dest: &mut Vec) { self.u.to_sponge_field_elements(dest); self.x.to_sponge_field_elements(dest); // We cannot call `to_native_sponge_field_elements(dest)` directly, as // `to_native_sponge_field_elements` needs `F` to be `C::ScalarField`, // but here `F` is a generic `PrimeField`. self.cmWE .to_native_sponge_field_elements_as_vec() .to_sponge_field_elements(dest); } } impl CommittedInstanceOps for CommittedInstance { type Var = CommittedInstanceVar; fn get_commitments(&self) -> Vec { vec![self.cmWE] } fn is_incoming(&self) -> bool { self.u == One::one() } } impl Inputize> for CommittedInstance { fn inputize(&self) -> Vec { [&[self.u][..], &self.x, &self.cmWE.inputize()].concat() } } /// A Witness in Ova is represented by `w`. It also contains a blinder which can or not be used /// when committing to the witness itself. #[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] pub struct Witness { pub w: Vec, pub rW: C::ScalarField, } impl Witness { /// Generates a new `Witness` instance from a given witness vector. /// If `H = true`, then we assume we want to blind it at commitment time, /// hence sampling `rW` from the randomness passed. pub fn new(w: Vec, mut rng: impl RngCore) -> Self { Self { w, rW: if H { C::ScalarField::rand(&mut rng) } else { C::ScalarField::zero() }, } } /// Given `x` (public inputs) and `t` or `e` (which we always concatenate in Ova) and the /// public inputs `x`, generates a [`CommittedInstance`] as a result which will or not be /// blinded depending on how the const generic `HC` is set up. pub fn commit, const HC: bool>( &self, params: &CS::ProverParams, x: Vec, t_or_e: Vec, ) -> Result, Error> { let cmWE = CS::commit(params, &[self.w.clone(), t_or_e].concat(), &self.rW)?; Ok(CommittedInstance { u: C::ScalarField::one(), cmWE, x, }) } } impl Dummy<&R1CS>> for Witness { fn dummy(r1cs: &R1CS>) -> Self { Self { w: vec![C::ScalarField::zero(); r1cs.A.n_cols - 1 - r1cs.l], rW: C::ScalarField::zero(), } } } /// Implements the NIFS (Non-Interactive Folding Scheme) trait for Ova. pub struct NIFS< C: CurveGroup, CS: CommitmentScheme, T: Transcript, const H: bool = false, > { _c: PhantomData, _cp: PhantomData, _t: PhantomData, } impl, T: Transcript, const H: bool> NIFSTrait for NIFS where ::ScalarField: Absorb, ::BaseField: PrimeField, { type CommittedInstance = CommittedInstance; type Witness = Witness; type ProverAux = (); // Proof is unused, but set to C::ScalarField so that the NIFSGadgetTrait abstraction can // define the ProofsVar implementing the AllocVar from Proof type Proof = C::ScalarField; fn new_witness(w: Vec, _e_len: usize, rng: impl RngCore) -> Self::Witness { Witness::new::(w, rng) } fn new_instance( _rng: impl RngCore, params: &CS::ProverParams, W: &Self::Witness, x: Vec, aux: Vec, // t_or_e ) -> Result { W.commit::(params, x, aux) } fn fold_witness( r: C::ScalarField, // in Ova's hackmd denoted as `alpha` W_i: &Self::Witness, w_i: &Self::Witness, _aux: &Self::ProverAux, ) -> Result { let w: Vec = W_i .w .iter() .zip(&w_i.w) .map(|(a, b)| *a + (r * b)) .collect(); let rW = W_i.rW + r * w_i.rW; Ok(Self::Witness { w, rW }) } fn prove( _cs_prover_params: &CS::ProverParams, _r1cs: &R1CS, transcript: &mut T, pp_hash: C::ScalarField, W_i: &Self::Witness, U_i: &Self::CommittedInstance, w_i: &Self::Witness, u_i: &Self::CommittedInstance, ) -> Result< ( Self::Witness, Self::CommittedInstance, Self::Proof, Vec, ), Error, > { let mut transcript_v = transcript.clone(); let r_bits = ChallengeGadget::::get_challenge_native( transcript, pp_hash, U_i, u_i, None, // cmT not used in Ova ); let r_Fr = C::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) .ok_or(Error::OutOfBounds)?; let w = Self::fold_witness(r_Fr, W_i, w_i, &())?; let proof = C::ScalarField::zero(); let (ci, _r_bits_v) = Self::verify(&mut transcript_v, pp_hash, U_i, u_i, &proof)?; #[cfg(test)] assert_eq!(_r_bits_v, r_bits); Ok((w, ci, proof, r_bits)) } fn verify( transcript: &mut T, pp_hash: C::ScalarField, U_i: &Self::CommittedInstance, u_i: &Self::CommittedInstance, _proof: &Self::Proof, // unused in Ova ) -> Result<(Self::CommittedInstance, Vec), Error> { let r_bits = ChallengeGadget::::get_challenge_native( transcript, pp_hash, U_i, u_i, None, // cmT not used in Ova ); let r = C::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) .ok_or(Error::OutOfBounds)?; // recall that r=alpha, and u=mu between Nova and Ova respectively let u = U_i.u + r; // u_i.u is always 1 in Ova as we just can do IVC (not PCD). let cmWE = U_i.cmWE + u_i.cmWE.mul(r); let x = U_i .x .iter() .zip(&u_i.x) .map(|(a, b)| *a + (r * b)) .collect::>(); Ok((Self::CommittedInstance { cmWE, u, x }, r_bits)) } } /// Computes the E parameter (error terms) for the given R1CS and the instance's z and u. This /// method is used by the verifier to obtain E in order to check the RelaxedR1CS relation. pub fn compute_E( r1cs: &R1CS, z: &[C::ScalarField], u: C::ScalarField, ) -> Result, Error> { let (A, B, C) = (r1cs.A.clone(), r1cs.B.clone(), r1cs.C.clone()); // this is parallelizable (for the future) let Az = mat_vec_mul(&A, z)?; let Bz = mat_vec_mul(&B, z)?; let Cz = mat_vec_mul(&C, z)?; let Az_Bz = hadamard(&Az, &Bz)?; let uCz = vec_scalar_mul(&Cz, &u); vec_sub(&Az_Bz, &uCz) } #[cfg(test)] pub mod tests { use super::*; use ark_pallas::{Fr, Projective}; use crate::arith::{r1cs::tests::get_test_r1cs, Arith}; use crate::commitment::pedersen::Pedersen; use crate::folding::nova::nifs::tests::test_nifs_opt; use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; // Simple auxiliary structure mainly used to help pass a witness for which we can check // easily an R1CS relation. // Notice that checking it requires us to have `E` as per [`Arith`] trait definition. // But since we don't hold `E` nor `e` within the NIFS, we create this structure to pass // `e` such that the check can be done. #[derive(Debug, Clone)] pub(crate) struct TestingWitness { pub(crate) w: Vec, pub(crate) e: Vec, } impl Arith, CommittedInstance> for R1CS> { type Evaluation = Vec>; fn eval_relation( &self, w: &TestingWitness, u: &CommittedInstance, ) -> Result { self.eval_at_z(&[&[u.u], u.x.as_slice(), &w.w].concat()) } fn check_evaluation( w: &TestingWitness, _u: &CommittedInstance, e: Self::Evaluation, ) -> Result<(), Error> { (w.e == e).then_some(()).ok_or(Error::NotSatisfied) } } #[test] fn test_nifs_ova() { let (W, U) = test_nifs_opt::, PoseidonSponge>>(); // check the last folded instance relation let r1cs = get_test_r1cs(); let z: Vec = [&[U.u][..], &U.x, &W.w].concat(); let e = compute_E::(&r1cs, &z, U.u).unwrap(); r1cs.check_relation(&TestingWitness:: { e, w: W.w.clone() }, &U) .unwrap(); } }