From 4789eefc3e9608ddebb2ecdf6c551705874d479d Mon Sep 17 00:00:00 2001 From: arnaucube Date: Wed, 2 Oct 2024 20:49:14 +0200 Subject: [PATCH] rm old Ova duplicated code This commit combined with the other ones (add nifs abstraction & port Ova to the nifs abstraction) allows to effectively get rid of ~400 lines of code that were duplicated in the Ova NIFS impl from the Nova impl. --- folding-schemes/src/folding/ova/mod.rs | 157 -------- folding-schemes/src/folding/ova/nifs.rs | 482 ------------------------ 2 files changed, 639 deletions(-) delete mode 100644 folding-schemes/src/folding/ova/mod.rs delete mode 100644 folding-schemes/src/folding/ova/nifs.rs diff --git a/folding-schemes/src/folding/ova/mod.rs b/folding-schemes/src/folding/ova/mod.rs deleted file mode 100644 index de109684..00000000 --- a/folding-schemes/src/folding/ova/mod.rs +++ /dev/null @@ -1,157 +0,0 @@ -use std::marker::PhantomData; - -/// Implements the scheme described in [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw?view). -/// Ova is a slight modification to Nova that shaves off 1 group operation and a few hashes. -/// The key idea is that we can commit to $T$ and $W$ in one commitment. -/// -/// This slightly breaks the abstraction between the IVC scheme and the accumulation scheme. -/// We assume that the accumulation prover receives $\vec{w}$ as input which proves the current step -/// of the computation and that the *previous* accumulation was performed correctly. -/// Note that $\vec{w}$ can be generated without being aware of $\vec{t}$ or $\alpha$. -/// Alternatively the accumulation prover can receive the commitment to $\vec{w}$ as input -/// and add to it the commitment to $\vec{t}$. -/// -/// This yields a commitment to the concatenated vector $\vec{w}||\vec{t}$. -/// This works because both $W$ and $T$ are multiplied by the same challenge $\alpha$ in Nova. -use ark_crypto_primitives::sponge::Absorb; -use ark_ec::{CurveGroup, Group}; -use ark_ff::PrimeField; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_std::fmt::Debug; -use ark_std::rand::RngCore; -use ark_std::{One, UniformRand, Zero}; - -use crate::arith::r1cs::R1CS; -use crate::commitment::CommitmentScheme; -use crate::constants::NOVA_N_BITS_RO; -use crate::folding::{circuits::CF1, traits::Dummy}; -use crate::transcript::{AbsorbNonNative, Transcript}; -use crate::Error; - -pub mod nifs; - -/// A CommittedInstance in [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw?view) 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 `mu`. -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct CommittedInstance { - pub mu: C::ScalarField, - 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.mu.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); - } -} - -// Clippy flag needed for now. -#[allow(dead_code)] -/// A Witness in [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw?view) 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() - }, - } - } - - /// Computes the `W` or `W'` commitment (The accumulated-instance W' or the incoming-instance W) - /// as specified in Ova. See: . - /// - /// This is the result of concatenating the accumulated-instance `w` vector with - /// `e` or `t`. - /// Generates a [`CommittedInstance`] as a result which will or not be blinded depending on how the - /// const generic `HC` is set up. - /// - /// This is the exact trick that allows Ova to save up 1 commitment with respect to Nova. - /// At the cost of loosing the PCD property and only maintaining the IVC one. - pub fn commit, const HC: bool>( - &self, - params: &CS::ProverParams, - t_or_e: Vec, - x: Vec, - ) -> Result, Error> { - let cmWE = CS::commit(params, &[self.w.clone(), t_or_e].concat(), &self.rW)?; - Ok(CommittedInstance { - mu: 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(), - } - } -} - -pub struct ChallengeGadget { - _c: PhantomData, -} -impl ChallengeGadget -where - C: CurveGroup, - ::BaseField: PrimeField, - ::ScalarField: Absorb, -{ - pub fn get_challenge_native>( - transcript: &mut T, - pp_hash: C::ScalarField, // public params hash - // Running instance - U_i: CommittedInstance, - // Incoming instance - u_i: CommittedInstance, - ) -> Vec { - // NOTICE: This isn't following the order of the HackMD. - // As long as we match it. We should not have any issues. - transcript.absorb(&pp_hash); - transcript.absorb(&U_i); - transcript.absorb(&u_i); - transcript.squeeze_bits(NOVA_N_BITS_RO) - } -} - -// 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, -} diff --git a/folding-schemes/src/folding/ova/nifs.rs b/folding-schemes/src/folding/ova/nifs.rs deleted file mode 100644 index 3672c19d..00000000 --- a/folding-schemes/src/folding/ova/nifs.rs +++ /dev/null @@ -1,482 +0,0 @@ -/// This module contains the implementation of the Ova scheme NIFS (Non-Interactive Folding Scheme) as -/// outlined in the protocol description doc: -/// authored by Benedikt Bünz. -use ark_crypto_primitives::sponge::Absorb; -use ark_ec::{CurveGroup, Group}; -use ark_std::One; -use std::marker::PhantomData; - -use super::{CommittedInstance, Witness}; -use crate::arith::r1cs::R1CS; -use crate::commitment::CommitmentScheme; -use crate::transcript::Transcript; -use crate::utils::vec::{hadamard, mat_vec_mul, vec_scalar_mul, vec_sub}; -use crate::Error; - -/// Implements all the operations executed by the Non-Interactive Folding Scheme described in the protocol -/// spec by Bünz in the [original HackMD](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw?both#Construction). -/// `H` specifies whether the NIFS will use a blinding factor -pub struct NIFS, const H: bool = false> { - _c: PhantomData, - _cp: PhantomData, -} - -impl, const H: bool> NIFS -where - ::ScalarField: Absorb, -{ - /// Computes the T parameter (Cross Terms) as in Nova. - /// The wrapper is only in place to facilitate the calling as we need - /// to reconstruct the `z`s being folded in order to compute T. - pub fn compute_T( - r1cs: &R1CS, - w_i: &Witness, - x_i: &[C::ScalarField], - W_i: &Witness, - X_i: &[C::ScalarField], - mu: C::ScalarField, - ) -> Result, Error> { - crate::folding::nova::nifs::NIFS::::compute_T( - r1cs, - C::ScalarField::one(), - mu, - &[vec![C::ScalarField::one()], x_i.to_vec(), w_i.w.to_vec()].concat(), - &[vec![mu], X_i.to_vec(), W_i.w.to_vec()].concat(), - ) - } - - /// Computes the E parameter (Error Terms) as in Nova. - /// The wrapper is only in place to facilitate the calling as we need - /// to reconstruct the `z`s being folded in order to compute E. - /// - /// Not only that, but notice that the incoming-instance `mu` parameter is always - /// equal to 1. Therefore, we can save the some computations. - pub fn compute_E( - r1cs: &R1CS, - W_i: &Witness, - X_i: &[C::ScalarField], - mu: C::ScalarField, - ) -> Result, Error> { - let (A, B, C) = (r1cs.A.clone(), r1cs.B.clone(), r1cs.C.clone()); - - let z_prime = [&[mu], X_i, &W_i.w].concat(); - // this is parallelizable (for the future) - let Az_prime = mat_vec_mul(&A, &z_prime)?; - let Bz_prime = mat_vec_mul(&B, &z_prime)?; - let Cz_prime = mat_vec_mul(&C, &z_prime)?; - - let Az_prime_Bz_prime = hadamard(&Az_prime, &Bz_prime)?; - let muCz_prime = vec_scalar_mul(&Cz_prime, &mu); - - vec_sub(&Az_prime_Bz_prime, &muCz_prime) - } - - /// Folds 2 [`CommittedInstance`]s returning a freshly folded one as is specified - /// in: . - /// Here, alpha is a randomness sampled from a [`Transcript`]. - pub fn fold_committed_instance( - alpha: C::ScalarField, - // This is W (incoming) - u_i: &CommittedInstance, - // This is W' (running) - U_i: &CommittedInstance, - ) -> CommittedInstance { - let mu = U_i.mu + alpha; // u_i.mu **IS ALWAYS 1 in OVA** as we just can do sequential IVC. - let cmWE = U_i.cmWE + u_i.cmWE.mul(alpha); - let x = U_i - .x - .iter() - .zip(&u_i.x) - .map(|(a, b)| *a + (alpha * b)) - .collect::>(); - - CommittedInstance:: { cmWE, mu, x } - } - - /// Folds 2 [`Witness`]s returning a freshly folded one as is specified - /// in: . - /// Here, alpha is a randomness sampled from a [`Transcript`]. - pub fn fold_witness( - alpha: C::ScalarField, - // incoming instance - w_i: &Witness, - // running instance - W_i: &Witness, - ) -> Result, Error> { - let w: Vec = W_i - .w - .iter() - .zip(&w_i.w) - .map(|(a, b)| *a + (alpha * b)) - .collect(); - - let rW = W_i.rW + alpha * w_i.rW; - Ok(Witness:: { w, rW }) - } - - /// fold_instances is part of the NIFS.P logic described in - /// [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw?both#Construction)'s Construction section. - /// It returns the folded [`CommittedInstance`] and [`Witness`]. - pub fn fold_instances( - r: C::ScalarField, - // incoming instance - w_i: &Witness, - u_i: &CommittedInstance, - // running instance - W_i: &Witness, - U_i: &CommittedInstance, - ) -> Result<(Witness, CommittedInstance), Error> { - // fold witness - let w3 = NIFS::::fold_witness(r, w_i, W_i)?; - - // fold committed instances - let ci3 = NIFS::::fold_committed_instance(r, u_i, U_i); - - Ok((w3, ci3)) - } - - /// Implements NIFS.V (accumulation verifier) logic described in [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw?both#Construction)'s - /// Construction section. - /// It returns the folded [`CommittedInstance`]. - pub fn verify( - // r comes from the transcript, and is a n-bit (N_BITS_CHALLENGE) element - alpha: C::ScalarField, - // incoming instance - u_i: &CommittedInstance, - // running instance - U_i: &CommittedInstance, - ) -> CommittedInstance { - NIFS::::fold_committed_instance(alpha, u_i, U_i) - } - - #[cfg(test)] - /// Verify committed folded instance (ui) relations. Notice that this method does not open the - /// commitments, but just checks that the given committed instances (ui1, ui2) when folded - /// result in the folded committed instance (ui3) values. - pub(crate) fn verify_folded_instance( - r: C::ScalarField, - // incoming instance - u_i: &CommittedInstance, - // running instance - U_i: &CommittedInstance, - // folded instance - folded_instance: &CommittedInstance, - ) -> Result<(), Error> { - let expected = Self::fold_committed_instance(r, u_i, U_i); - if folded_instance.mu != expected.mu - || folded_instance.cmWE != expected.cmWE - || folded_instance.x != expected.x - { - return Err(Error::NotSatisfied); - } - Ok(()) - } - - /// Generates a [`CS::Proof`] for the given [`CommittedInstance`] and [`Witness`] pair. - pub fn prove_commitment( - r1cs: &R1CS, - tr: &mut impl Transcript, - cs_prover_params: &CS::ProverParams, - w: &Witness, - ci: &CommittedInstance, - ) -> Result { - let e = NIFS::::compute_E(r1cs, w, &ci.x, ci.mu).unwrap(); - let w_concat_e: Vec = [w.w.clone(), e].concat(); - CS::prove(cs_prover_params, tr, &ci.cmWE, &w_concat_e, &w.rW, None) - } -} - -#[cfg(test)] -pub mod tests { - use super::*; - use crate::folding::ova::ChallengeGadget; - use crate::transcript::poseidon::poseidon_canonical_config; - use crate::{ - arith::{ - r1cs::tests::{get_test_r1cs, get_test_z}, - Arith, - }, - folding::traits::Dummy, - }; - use crate::{ - commitment::pedersen::{Params as PedersenParams, Pedersen}, - folding::ova::TestingWitness, - }; - use ark_crypto_primitives::sponge::{ - poseidon::{PoseidonConfig, PoseidonSponge}, - CryptographicSponge, - }; - use ark_ff::{BigInteger, PrimeField}; - use ark_pallas::{Fr, Projective}; - use ark_std::{test_rng, UniformRand, Zero}; - - fn compute_E_check_relation, const H: bool>( - r1cs: &R1CS, - w: &Witness, - u: &CommittedInstance, - ) where - ::ScalarField: Absorb, - { - let e = NIFS::::compute_E(r1cs, w, &u.x, u.mu).unwrap(); - r1cs.check_relation(&TestingWitness:: { e, w: w.w.clone() }, u) - .unwrap(); - } - - #[allow(clippy::type_complexity)] - pub(crate) fn prepare_simple_fold_inputs() -> ( - PedersenParams, - PoseidonConfig, - R1CS, - Witness, // w - CommittedInstance, // u - Witness, // W - CommittedInstance, // U - Witness, // w_fold - CommittedInstance, // u_fold - Vec, // r_bits - C::ScalarField, // r_Fr - ) - where - C: CurveGroup, - ::BaseField: PrimeField, - C::ScalarField: Absorb, - { - // Index 1 represents the incoming instance - // Index 2 represents the accumulated instance - let r1cs = get_test_r1cs(); - let z1 = get_test_z(3); - let z2 = get_test_z(4); - let (w1, x1) = r1cs.split_z(&z1); - let (w2, x2) = r1cs.split_z(&z2); - - let w = Witness::::new::(w1.clone(), test_rng()); - let W: Witness = Witness::::new::(w2.clone(), test_rng()); - - let mut rng = ark_std::test_rng(); - let (pedersen_params, _) = Pedersen::::setup(&mut rng, r1cs.A.n_cols).unwrap(); - - // In order to be able to compute the committed instances, we need to compute `t` and `e` - // compute t - let t = NIFS::>::compute_T(&r1cs, &w, &x1, &W, &x2, C::ScalarField::one()) - .unwrap(); - // compute e (mu is 1 although is the running instance as we are "crafting it"). - let e = NIFS::>::compute_E(&r1cs, &W, &x2, C::ScalarField::one()).unwrap(); - // compute committed instances - // Incoming-instance - let u = w - .commit::, false>(&pedersen_params, t, x1.clone()) - .unwrap(); - // Running-instance - let U = W - .commit::, false>(&pedersen_params, e, x2.clone()) - .unwrap(); - - let poseidon_config = poseidon_canonical_config::(); - let mut transcript = PoseidonSponge::::new(&poseidon_config); - - let pp_hash = C::ScalarField::from(42u32); // only for test - - let alpha_bits = ChallengeGadget::::get_challenge_native( - &mut transcript, - pp_hash, - u.clone(), - U.clone(), - ); - let alpha_Fr = C::ScalarField::from_bigint(BigInteger::from_bits_le(&alpha_bits)).unwrap(); - - let (w_fold, u_fold) = - NIFS::, false>::fold_instances(alpha_Fr, &w, &u, &W, &U).unwrap(); - - // Check correctness of the R1CS relation of the folded instance. - compute_E_check_relation::, false>(&r1cs, &w_fold, &u_fold); - - ( - pedersen_params, - poseidon_config, - r1cs, - w, - u, - W, - U, - w_fold, - u_fold, - alpha_bits, - alpha_Fr, - ) - } - - // fold 2 dummy instances and check that the folded instance holds the relaxed R1CS relation - #[test] - fn test_nifs_fold_dummy() { - let mut rng = ark_std::test_rng(); - let r1cs = get_test_r1cs::(); - - let w_dummy = Witness::::dummy(&r1cs); - let (pedersen_params, _) = Pedersen::::setup(&mut rng, r1cs.A.n_cols).unwrap(); - - // In order to be able to compute the committed instances, we need to compute `t` and `e` - // compute t - let t = NIFS::>::compute_T( - &r1cs, - &w_dummy, - &[Fr::zero()], - &w_dummy, - &[Fr::zero()], - Fr::one(), - ) - .unwrap(); - - // compute e - let e = NIFS::>::compute_E( - &r1cs, - &w_dummy, - &[Fr::zero()], - Fr::one(), - ) - .unwrap(); - - // dummy incoming instance, witness and public inputs - let u_dummy = w_dummy - .commit::, false>(&pedersen_params, t, vec![Fr::zero()]) - .unwrap(); - - // dummy accumulated instance, witness and public inputs - let U_dummy = w_dummy - .commit::, false>(&pedersen_params, e.clone(), vec![Fr::zero()]) - .unwrap(); - - let w_i = w_dummy.clone(); - let u_i = u_dummy.clone(); - let W_i = w_dummy.clone(); - let U_i = U_dummy.clone(); - - // Check correctness of the R1CS relations of both instances. - compute_E_check_relation::, false>(&r1cs, &w_i, &u_i); - compute_E_check_relation::, false>(&r1cs, &W_i, &U_i); - - // NIFS.P - let r_Fr = Fr::from(3_u32); - let (w_fold, u_fold) = - NIFS::>::fold_instances(r_Fr, &w_i, &u_i, &W_i, &U_i) - .unwrap(); - - // Check correctness of the R1CS relation of both instances. - compute_E_check_relation::, false>( - &r1cs, &w_fold, &u_fold, - ); - } - - // fold 2 instances into one - #[test] - fn test_nifs_one_fold() { - let (pedersen_params, poseidon_config, r1cs, w, u, W, U, w_fold, u_fold, _, r) = - prepare_simple_fold_inputs(); - - // NIFS.V - let u_fold_v = NIFS::>::verify(r, &u, &U); - assert_eq!(u_fold_v, u_fold); - - // Check that relations hold for the 2 inputted instances and the folded one - compute_E_check_relation::, false>(&r1cs, &w, &u); - compute_E_check_relation::, false>(&r1cs, &W, &U); - compute_E_check_relation::, false>( - &r1cs, &w_fold, &u_fold, - ); - - // check that folded commitments from folded instance (u) are equal to folding the - // use folded rW to commit w_fold - let e_fold = NIFS::>::compute_E( - &r1cs, &w_fold, &u_fold.x, u_fold.mu, - ) - .unwrap(); - let mut u_fold_expected = w_fold - .commit::, false>(&pedersen_params, e_fold, u_fold.x.clone()) - .unwrap(); - u_fold_expected.mu = u_fold.mu; - assert_eq!(u_fold_expected.cmWE, u_fold.cmWE); - - // NIFS.Verify_Folded_Instance: - NIFS::>::verify_folded_instance(r, &u, &U, &u_fold) - .unwrap(); - - // init Prover's transcript - let mut transcript_p = PoseidonSponge::::new(&poseidon_config); - // init Verifier's transcript - let mut transcript_v = PoseidonSponge::::new(&poseidon_config); - - // prove the u_fold.cmWE - let cm_proof = NIFS::>::prove_commitment( - &r1cs, - &mut transcript_p, - &pedersen_params, - &w_fold, - &u_fold, - ) - .unwrap(); - - // verify the u_fold.cmWE. - Pedersen::::verify( - &pedersen_params, - &mut transcript_v, - &u_fold.cmWE, - &cm_proof, - ) - .unwrap(); - } - - #[test] - fn test_nifs_fold_loop() { - let r1cs = get_test_r1cs(); - let z = get_test_z(3); - let (w, x) = r1cs.split_z(&z); - - let mut rng = ark_std::test_rng(); - let (pedersen_params, _) = Pedersen::::setup(&mut rng, r1cs.A.n_cols).unwrap(); - - // prepare the running instance - let mut W = Witness::::new::(w.clone(), &mut rng); - // Compute e - let e = - NIFS::>::compute_E(&r1cs, &W, &x, Fr::one()).unwrap(); - // Compute running `CommittedInstance`. - let mut U = W - .commit::, false>(&pedersen_params, e, x) - .unwrap(); - - let num_iters = 10; - for i in 0..num_iters { - // prepare the incoming instance - let incoming_instance_z = get_test_z(i + 4); - let (w, x) = r1cs.split_z(&incoming_instance_z); - let w = Witness::::new::(w, test_rng()); - let t = - NIFS::>::compute_T(&r1cs, &w, &U.x, &w, &x, U.mu) - .unwrap(); - let u = w - .commit::, false>(&pedersen_params, t, x) - .unwrap(); - - // Check incoming instance is Ok. - compute_E_check_relation::, false>(&r1cs, &w, &u); - - // Generate "transcript randomness" - let alpha = Fr::rand(&mut rng); // folding challenge would come from the RO - - // NIFS.P - let (w_folded, _) = - NIFS::>::fold_instances(alpha, &w, &u, &W, &u) - .unwrap(); - - // NIFS.V - let u_folded = NIFS::>::verify(alpha, &u, &U); - - compute_E_check_relation::, false>( - &r1cs, &w_folded, &u_folded, - ); - - // set running_instance for next loop iteration - W = w_folded; - U = u_folded; - } - } -}