* Adding Mova Co-Authored-By: Togzhan Barakbayeva <45527668+btogzhan2000@users.noreply.github.com> Co-Authored-By: Ilia Vlasov <5365540+elijahvlasov@users.noreply.github.com> Co-Authored-By: matthew-a-klein <96837318+matthew-a-klein@users.noreply.github.com> * Fix CLI * Updated from main * Solution to stop the CLI from complaining about deadcode PR comment Co-authored-by: arnaucube <root@arnaucube.com> * Requested changes and update from main * Refactor NIFSTrait & port Mova impl to it * refactor NIFSTrait interface to fit Nova variants (Nova,Mova,Ova) Refactor NIFSTrait interface to fit Nova variants (Nova,Mova,Ova). The relevant change is instead of passing the challenge as input, now it passes the transcript and computes the challenges internally (Nova & Ova still compute a single challenge, but Mova computes multiple while absorbing at different steps). * port Mova impl to the NIFSTrait * remove unnecessary wrappers in the nova/zk.rs * remove Nova NIFS methods that are no longer needed after the refactor * put together the different NIFS implementations (Nova, Mova, Ova) so that they can interchanged at usage. The idea is that Nova and its variants (Ova & Mova) share most of the logic for the circuits & IVC & Deciders, so with the abstracted NIFS interface we will be able to reuse most of the already existing Nova code for having the Mova & Ova circuits, IVC, and Decider. * adapt Nova's DeciderEth prepare_calldata & update examples to it * small update to fix solidity tests --------- Co-authored-by: Togzhan Barakbayeva <45527668+btogzhan2000@users.noreply.github.com> Co-authored-by: Ilia Vlasov <5365540+elijahvlasov@users.noreply.github.com> Co-authored-by: matthew-a-klein <96837318+matthew-a-klein@users.noreply.github.com> Co-authored-by: arnaucube <root@arnaucube.com> Co-authored-by: arnaucube <git@arnaucube.com>main
@ -0,0 +1,152 @@ |
|||||
|
/// This module defines the NIFSTrait, which is set to implement the NIFS (Non-Interactive Folding
|
||||
|
/// Scheme) by the various schemes (Nova, Mova, Ova).
|
||||
|
use ark_crypto_primitives::sponge::Absorb;
|
||||
|
use ark_ec::CurveGroup;
|
||||
|
use ark_std::fmt::Debug;
|
||||
|
use ark_std::rand::RngCore;
|
||||
|
|
||||
|
use crate::arith::r1cs::R1CS;
|
||||
|
use crate::commitment::CommitmentScheme;
|
||||
|
use crate::transcript::Transcript;
|
||||
|
use crate::Error;
|
||||
|
|
||||
|
pub mod mova;
|
||||
|
pub mod nova;
|
||||
|
pub mod ova;
|
||||
|
pub mod pointvsline;
|
||||
|
|
||||
|
/// Defines the NIFS (Non-Interactive Folding Scheme) trait, initially defined in
|
||||
|
/// [Nova](https://eprint.iacr.org/2021/370.pdf), and it's variants
|
||||
|
/// [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw) and
|
||||
|
/// [Mova](https://eprint.iacr.org/2024/1220.pdf).
|
||||
|
/// `H` specifies whether the NIFS will use a blinding factor.
|
||||
|
pub trait NIFSTrait<
|
||||
|
C: CurveGroup,
|
||||
|
CS: CommitmentScheme<C, H>,
|
||||
|
T: Transcript<C::ScalarField>,
|
||||
|
const H: bool = false,
|
||||
|
>
|
||||
|
{
|
||||
|
type CommittedInstance: Debug + Clone + Absorb;
|
||||
|
type Witness: Debug + Clone;
|
||||
|
type ProverAux: Debug + Clone; // Prover's aux params. eg. in Nova is T
|
||||
|
type Proof: Debug + Clone; // proof. eg. in Nova is cmT
|
||||
|
|
||||
|
fn new_witness(w: Vec<C::ScalarField>, e_len: usize, rng: impl RngCore) -> Self::Witness;
|
||||
|
|
||||
|
fn new_instance(
|
||||
|
rng: impl RngCore,
|
||||
|
params: &CS::ProverParams,
|
||||
|
w: &Self::Witness,
|
||||
|
x: Vec<C::ScalarField>,
|
||||
|
aux: Vec<C::ScalarField>, // t_or_e in Ova, empty for Nova
|
||||
|
) -> Result<Self::CommittedInstance, Error>;
|
||||
|
|
||||
|
fn fold_witness(
|
||||
|
r: C::ScalarField,
|
||||
|
W: &Self::Witness, // running witness
|
||||
|
w: &Self::Witness, // incoming witness
|
||||
|
aux: &Self::ProverAux,
|
||||
|
) -> Result<Self::Witness, Error>;
|
||||
|
|
||||
|
/// NIFS.P. Returns a tuple containing the folded Witness, the folded CommittedInstance, and
|
||||
|
/// the used challenge `r` as a vector of bits, so that it can be reused in other methods.
|
||||
|
#[allow(clippy::type_complexity)]
|
||||
|
#[allow(clippy::too_many_arguments)]
|
||||
|
fn prove(
|
||||
|
cs_prover_params: &CS::ProverParams,
|
||||
|
r1cs: &R1CS<C::ScalarField>,
|
||||
|
transcript: &mut T,
|
||||
|
pp_hash: C::ScalarField,
|
||||
|
W_i: &Self::Witness, // running witness
|
||||
|
U_i: &Self::CommittedInstance, // running committed instance
|
||||
|
w_i: &Self::Witness, // incoming witness
|
||||
|
u_i: &Self::CommittedInstance, // incoming committed instance
|
||||
|
) -> Result<
|
||||
|
(
|
||||
|
Self::Witness,
|
||||
|
Self::CommittedInstance,
|
||||
|
Self::Proof,
|
||||
|
Vec<bool>,
|
||||
|
),
|
||||
|
Error,
|
||||
|
>;
|
||||
|
|
||||
|
/// NIFS.V. Returns the folded CommittedInstance and the used challenge `r` as a vector of
|
||||
|
/// bits, so that it can be reused in other methods.
|
||||
|
fn verify(
|
||||
|
transcript: &mut T,
|
||||
|
pp_hash: C::ScalarField,
|
||||
|
U_i: &Self::CommittedInstance,
|
||||
|
u_i: &Self::CommittedInstance,
|
||||
|
proof: &Self::Proof,
|
||||
|
) -> Result<(Self::CommittedInstance, Vec<bool>), Error>;
|
||||
|
}
|
||||
|
|
||||
|
#[cfg(test)]
|
||||
|
pub mod tests {
|
||||
|
use super::*;
|
||||
|
use crate::transcript::poseidon::poseidon_canonical_config;
|
||||
|
use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, CryptographicSponge};
|
||||
|
use ark_pallas::{Fr, Projective};
|
||||
|
use ark_std::{test_rng, UniformRand};
|
||||
|
|
||||
|
use super::NIFSTrait;
|
||||
|
use crate::arith::r1cs::tests::{get_test_r1cs, get_test_z};
|
||||
|
use crate::commitment::pedersen::Pedersen;
|
||||
|
|
||||
|
/// Test method used to test the different implementations of the NIFSTrait (ie. Nova, Mova,
|
||||
|
/// Ova). Runs a loop using the NIFS trait, and returns the last Witness and CommittedInstance
|
||||
|
/// so that their relation can be checked.
|
||||
|
pub(crate) fn test_nifs_opt<
|
||||
|
N: NIFSTrait<Projective, Pedersen<Projective>, PoseidonSponge<Fr>>,
|
||||
|
>() -> (N::Witness, N::CommittedInstance) {
|
||||
|
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::<Projective>::setup(&mut rng, r1cs.A.n_cols).unwrap();
|
||||
|
|
||||
|
let poseidon_config = poseidon_canonical_config::<Fr>();
|
||||
|
let mut transcript_p = PoseidonSponge::<Fr>::new(&poseidon_config);
|
||||
|
let mut transcript_v = PoseidonSponge::<Fr>::new(&poseidon_config);
|
||||
|
let pp_hash = Fr::rand(&mut rng);
|
||||
|
|
||||
|
// prepare the running instance
|
||||
|
let mut W_i = N::new_witness(w.clone(), r1cs.A.n_rows, test_rng());
|
||||
|
let mut U_i = N::new_instance(&mut rng, &pedersen_params, &W_i, x, vec![]).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_i = N::new_witness(w.clone(), r1cs.A.n_rows, test_rng());
|
||||
|
let u_i = N::new_instance(&mut rng, &pedersen_params, &w_i, x, vec![]).unwrap();
|
||||
|
|
||||
|
// NIFS.P
|
||||
|
let (folded_witness, _, proof, _) = N::prove(
|
||||
|
&pedersen_params,
|
||||
|
&r1cs,
|
||||
|
&mut transcript_p,
|
||||
|
pp_hash,
|
||||
|
&W_i,
|
||||
|
&U_i,
|
||||
|
&w_i,
|
||||
|
&u_i,
|
||||
|
)
|
||||
|
.unwrap();
|
||||
|
|
||||
|
// NIFS.V
|
||||
|
let (folded_committed_instance, _) =
|
||||
|
N::verify(&mut transcript_v, pp_hash, &U_i, &u_i, &proof).unwrap();
|
||||
|
|
||||
|
// set running_instance for next loop iteration
|
||||
|
W_i = folded_witness;
|
||||
|
U_i = folded_committed_instance;
|
||||
|
}
|
||||
|
|
||||
|
(W_i, U_i)
|
||||
|
}
|
||||
|
}
|
@ -0,0 +1,411 @@ |
|||||
|
/// This module contains the implementation the NIFSTrait for the
|
||||
|
/// [Mova](https://eprint.iacr.org/2024/1220.pdf) NIFS (Non-Interactive Folding Scheme).
|
||||
|
use ark_crypto_primitives::sponge::Absorb;
|
||||
|
use ark_ec::{CurveGroup, Group};
|
||||
|
use ark_ff::PrimeField;
|
||||
|
use ark_poly::MultilinearExtension;
|
||||
|
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
|
||||
|
use ark_std::log2;
|
||||
|
use ark_std::rand::RngCore;
|
||||
|
use ark_std::{One, UniformRand, Zero};
|
||||
|
use std::marker::PhantomData;
|
||||
|
|
||||
|
use super::{
|
||||
|
nova::NIFS as NovaNIFS,
|
||||
|
pointvsline::{PointVsLine, PointVsLineProof, PointvsLineEvaluationClaim},
|
||||
|
NIFSTrait,
|
||||
|
};
|
||||
|
use crate::arith::{r1cs::R1CS, Arith};
|
||||
|
use crate::commitment::CommitmentScheme;
|
||||
|
use crate::folding::circuits::CF1;
|
||||
|
use crate::folding::traits::Dummy;
|
||||
|
use crate::transcript::AbsorbNonNative;
|
||||
|
use crate::transcript::Transcript;
|
||||
|
use crate::utils::{
|
||||
|
mle::dense_vec_to_dense_mle,
|
||||
|
vec::{is_zero_vec, vec_add, vec_scalar_mul},
|
||||
|
};
|
||||
|
use crate::Error;
|
||||
|
|
||||
|
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
|
||||
|
pub struct CommittedInstance<C: CurveGroup> {
|
||||
|
// Random evaluation point for the E
|
||||
|
pub rE: Vec<C::ScalarField>,
|
||||
|
// mleE is the evaluation of the MLE of E at r_E
|
||||
|
pub mleE: C::ScalarField,
|
||||
|
pub u: C::ScalarField,
|
||||
|
pub cmW: C,
|
||||
|
pub x: Vec<C::ScalarField>,
|
||||
|
}
|
||||
|
|
||||
|
impl<C: CurveGroup> Absorb for CommittedInstance<C>
|
||||
|
where
|
||||
|
C::ScalarField: Absorb,
|
||||
|
{
|
||||
|
fn to_sponge_bytes(&self, _dest: &mut Vec<u8>) {
|
||||
|
// This is never called
|
||||
|
unimplemented!()
|
||||
|
}
|
||||
|
|
||||
|
fn to_sponge_field_elements<F: PrimeField>(&self, dest: &mut Vec<F>) {
|
||||
|
self.u.to_sponge_field_elements(dest);
|
||||
|
self.x.to_sponge_field_elements(dest);
|
||||
|
self.rE.to_sponge_field_elements(dest);
|
||||
|
self.mleE.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.cmW
|
||||
|
.to_native_sponge_field_elements_as_vec()
|
||||
|
.to_sponge_field_elements(dest);
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<C: CurveGroup> Dummy<usize> for CommittedInstance<C> {
|
||||
|
fn dummy(io_len: usize) -> Self {
|
||||
|
Self {
|
||||
|
rE: vec![C::ScalarField::zero(); io_len],
|
||||
|
mleE: C::ScalarField::zero(),
|
||||
|
u: C::ScalarField::zero(),
|
||||
|
cmW: C::zero(),
|
||||
|
x: vec![C::ScalarField::zero(); io_len],
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
|
||||
|
pub struct Witness<C: CurveGroup> {
|
||||
|
pub E: Vec<C::ScalarField>,
|
||||
|
pub W: Vec<C::ScalarField>,
|
||||
|
pub rW: C::ScalarField,
|
||||
|
}
|
||||
|
|
||||
|
impl<C: CurveGroup> Dummy<&R1CS<C::ScalarField>> for Witness<C> {
|
||||
|
fn dummy(r1cs: &R1CS<C::ScalarField>) -> Self {
|
||||
|
Self {
|
||||
|
E: vec![C::ScalarField::zero(); r1cs.A.n_rows],
|
||||
|
W: vec![C::ScalarField::zero(); r1cs.A.n_cols - 1 - r1cs.l],
|
||||
|
rW: C::ScalarField::zero(),
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<C: CurveGroup> Witness<C> {
|
||||
|
pub fn new<const H: bool>(w: Vec<C::ScalarField>, e_len: usize, mut rng: impl RngCore) -> Self {
|
||||
|
let rW = if H {
|
||||
|
C::ScalarField::rand(&mut rng)
|
||||
|
} else {
|
||||
|
C::ScalarField::zero()
|
||||
|
};
|
||||
|
|
||||
|
Self {
|
||||
|
E: vec![C::ScalarField::zero(); e_len],
|
||||
|
W: w,
|
||||
|
rW,
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
pub fn commit<CS: CommitmentScheme<C, H>, const H: bool>(
|
||||
|
&self,
|
||||
|
params: &CS::ProverParams,
|
||||
|
x: Vec<C::ScalarField>,
|
||||
|
rE: Vec<C::ScalarField>,
|
||||
|
) -> Result<CommittedInstance<C>, Error> {
|
||||
|
let mut mleE = C::ScalarField::zero();
|
||||
|
if !is_zero_vec::<C::ScalarField>(&self.E) {
|
||||
|
let E = dense_vec_to_dense_mle(log2(self.E.len()) as usize, &self.E);
|
||||
|
mleE = E.evaluate(&rE).ok_or(Error::NotExpectedLength(
|
||||
|
rE.len(),
|
||||
|
log2(self.E.len()) as usize,
|
||||
|
))?;
|
||||
|
}
|
||||
|
let cmW = CS::commit(params, &self.W, &self.rW)?;
|
||||
|
Ok(CommittedInstance {
|
||||
|
rE,
|
||||
|
mleE,
|
||||
|
u: C::ScalarField::one(),
|
||||
|
cmW,
|
||||
|
x,
|
||||
|
})
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
|
||||
|
pub struct Proof<C: CurveGroup> {
|
||||
|
pub h_proof: PointVsLineProof<C>,
|
||||
|
pub mleE1_prime: C::ScalarField,
|
||||
|
pub mleE2_prime: C::ScalarField,
|
||||
|
pub mleT: C::ScalarField,
|
||||
|
}
|
||||
|
|
||||
|
/// Implements the Non-Interactive Folding Scheme described in section 4 of
|
||||
|
/// [Mova](https://eprint.iacr.org/2024/1220.pdf).
|
||||
|
/// `H` specifies whether the NIFS will use a blinding factor
|
||||
|
pub struct NIFS<
|
||||
|
C: CurveGroup,
|
||||
|
CS: CommitmentScheme<C, H>,
|
||||
|
T: Transcript<C::ScalarField>,
|
||||
|
const H: bool = false,
|
||||
|
> {
|
||||
|
_c: PhantomData<C>,
|
||||
|
_cp: PhantomData<CS>,
|
||||
|
_ct: PhantomData<T>,
|
||||
|
}
|
||||
|
|
||||
|
impl<C: CurveGroup, CS: CommitmentScheme<C, H>, T: Transcript<C::ScalarField>, const H: bool>
|
||||
|
NIFSTrait<C, CS, T, H> for NIFS<C, CS, T, H>
|
||||
|
where
|
||||
|
<C as Group>::ScalarField: Absorb,
|
||||
|
<C as CurveGroup>::BaseField: PrimeField,
|
||||
|
{
|
||||
|
type CommittedInstance = CommittedInstance<C>;
|
||||
|
type Witness = Witness<C>;
|
||||
|
type ProverAux = Vec<C::ScalarField>; // T in Mova's notation
|
||||
|
type Proof = Proof<C>;
|
||||
|
|
||||
|
fn new_witness(w: Vec<C::ScalarField>, e_len: usize, rng: impl RngCore) -> Self::Witness {
|
||||
|
Witness::new::<H>(w, e_len, rng)
|
||||
|
}
|
||||
|
|
||||
|
fn new_instance(
|
||||
|
mut rng: impl RngCore,
|
||||
|
params: &CS::ProverParams,
|
||||
|
W: &Self::Witness,
|
||||
|
x: Vec<C::ScalarField>,
|
||||
|
aux: Vec<C::ScalarField>, // = r_E
|
||||
|
) -> Result<Self::CommittedInstance, Error> {
|
||||
|
let mut rE = aux.clone();
|
||||
|
if is_zero_vec(&rE) {
|
||||
|
// means that we're in a fresh instance, so generate random value
|
||||
|
rE = (0..log2(W.E.len()))
|
||||
|
.map(|_| C::ScalarField::rand(&mut rng))
|
||||
|
.collect();
|
||||
|
}
|
||||
|
|
||||
|
W.commit::<CS, H>(params, x, rE)
|
||||
|
}
|
||||
|
|
||||
|
// Protocol 7 - point 3 (16)
|
||||
|
fn fold_witness(
|
||||
|
a: C::ScalarField,
|
||||
|
W_i: &Witness<C>,
|
||||
|
w_i: &Witness<C>,
|
||||
|
aux: &Vec<C::ScalarField>, // T in Mova's notation
|
||||
|
) -> Result<Witness<C>, Error> {
|
||||
|
let a2 = a * a;
|
||||
|
let E: Vec<C::ScalarField> = vec_add(
|
||||
|
&vec_add(&W_i.E, &vec_scalar_mul(aux, &a))?,
|
||||
|
&vec_scalar_mul(&w_i.E, &a2),
|
||||
|
)?;
|
||||
|
let W: Vec<C::ScalarField> = W_i
|
||||
|
.W
|
||||
|
.iter()
|
||||
|
.zip(&w_i.W)
|
||||
|
.map(|(i1, i2)| *i1 + (a * i2))
|
||||
|
.collect();
|
||||
|
|
||||
|
let rW = W_i.rW + a * w_i.rW;
|
||||
|
Ok(Witness::<C> { E, W, rW })
|
||||
|
}
|
||||
|
|
||||
|
/// [Mova](https://eprint.iacr.org/2024/1220.pdf)'s section 4. Protocol 8
|
||||
|
/// Returns a proof for the pt-vs-line operations along with the folded committed instance
|
||||
|
/// instances and witness
|
||||
|
#[allow(clippy::type_complexity)]
|
||||
|
fn prove(
|
||||
|
_cs_prover_params: &CS::ProverParams, // not used in Mova since we don't commit to T
|
||||
|
r1cs: &R1CS<C::ScalarField>,
|
||||
|
transcript: &mut T,
|
||||
|
pp_hash: C::ScalarField,
|
||||
|
W_i: &Witness<C>,
|
||||
|
U_i: &CommittedInstance<C>,
|
||||
|
w_i: &Witness<C>,
|
||||
|
u_i: &CommittedInstance<C>,
|
||||
|
) -> Result<
|
||||
|
(
|
||||
|
Self::Witness,
|
||||
|
Self::CommittedInstance,
|
||||
|
Self::Proof,
|
||||
|
Vec<bool>,
|
||||
|
),
|
||||
|
Error,
|
||||
|
> {
|
||||
|
transcript.absorb(&pp_hash);
|
||||
|
// Protocol 5 is pre-processing
|
||||
|
transcript.absorb(U_i);
|
||||
|
transcript.absorb(u_i);
|
||||
|
|
||||
|
// Protocol 6
|
||||
|
let (
|
||||
|
h_proof,
|
||||
|
PointvsLineEvaluationClaim {
|
||||
|
mleE1_prime,
|
||||
|
mleE2_prime,
|
||||
|
rE_prime,
|
||||
|
},
|
||||
|
) = PointVsLine::<C, T>::prove(transcript, U_i, u_i, W_i, w_i)?;
|
||||
|
|
||||
|
// Protocol 7
|
||||
|
|
||||
|
transcript.absorb(&mleE1_prime);
|
||||
|
transcript.absorb(&mleE2_prime);
|
||||
|
|
||||
|
// compute the cross terms
|
||||
|
let z1: Vec<C::ScalarField> = [vec![U_i.u], U_i.x.to_vec(), W_i.W.to_vec()].concat();
|
||||
|
let z2: Vec<C::ScalarField> = [vec![u_i.u], u_i.x.to_vec(), w_i.W.to_vec()].concat();
|
||||
|
let T = NovaNIFS::<C, CS, T, H>::compute_T(r1cs, U_i.u, u_i.u, &z1, &z2)?;
|
||||
|
|
||||
|
let n_vars: usize = log2(W_i.E.len()) as usize;
|
||||
|
if log2(T.len()) as usize != n_vars {
|
||||
|
return Err(Error::NotExpectedLength(T.len(), n_vars));
|
||||
|
}
|
||||
|
|
||||
|
let mleT = dense_vec_to_dense_mle(n_vars, &T);
|
||||
|
let mleT_evaluated = mleT.evaluate(&rE_prime).ok_or(Error::EvaluationFail)?;
|
||||
|
|
||||
|
transcript.absorb(&mleT_evaluated);
|
||||
|
|
||||
|
let alpha: C::ScalarField = transcript.get_challenge();
|
||||
|
|
||||
|
let ci = Self::fold_committed_instance(
|
||||
|
alpha,
|
||||
|
U_i,
|
||||
|
u_i,
|
||||
|
&rE_prime,
|
||||
|
&mleE1_prime,
|
||||
|
&mleE2_prime,
|
||||
|
&mleT_evaluated,
|
||||
|
)?;
|
||||
|
let w = Self::fold_witness(alpha, W_i, w_i, &T)?;
|
||||
|
|
||||
|
let proof = Self::Proof {
|
||||
|
h_proof,
|
||||
|
mleE1_prime,
|
||||
|
mleE2_prime,
|
||||
|
mleT: mleT_evaluated,
|
||||
|
};
|
||||
|
Ok((
|
||||
|
w,
|
||||
|
ci,
|
||||
|
proof,
|
||||
|
vec![], // r_bits, returned to be passed as inputs to the circuit, not used at the
|
||||
|
// current impl status
|
||||
|
))
|
||||
|
}
|
||||
|
|
||||
|
/// [Mova](https://eprint.iacr.org/2024/1220.pdf)'s section 4. It verifies the results from the proof
|
||||
|
/// Both the folding and the pt-vs-line proof
|
||||
|
/// returns the folded committed instance
|
||||
|
fn verify(
|
||||
|
transcript: &mut T,
|
||||
|
pp_hash: C::ScalarField,
|
||||
|
U_i: &CommittedInstance<C>,
|
||||
|
u_i: &CommittedInstance<C>,
|
||||
|
proof: &Proof<C>,
|
||||
|
) -> Result<(Self::CommittedInstance, Vec<bool>), Error> {
|
||||
|
transcript.absorb(&pp_hash);
|
||||
|
transcript.absorb(U_i);
|
||||
|
transcript.absorb(u_i);
|
||||
|
let rE_prime = PointVsLine::<C, T>::verify(
|
||||
|
transcript,
|
||||
|
U_i,
|
||||
|
u_i,
|
||||
|
&proof.h_proof,
|
||||
|
&proof.mleE1_prime,
|
||||
|
&proof.mleE2_prime,
|
||||
|
)?;
|
||||
|
|
||||
|
transcript.absorb(&proof.mleE1_prime);
|
||||
|
transcript.absorb(&proof.mleE2_prime);
|
||||
|
transcript.absorb(&proof.mleT);
|
||||
|
|
||||
|
let alpha: C::ScalarField = transcript.get_challenge();
|
||||
|
|
||||
|
Ok((
|
||||
|
Self::fold_committed_instance(
|
||||
|
alpha,
|
||||
|
U_i,
|
||||
|
u_i,
|
||||
|
&rE_prime,
|
||||
|
&proof.mleE1_prime,
|
||||
|
&proof.mleE2_prime,
|
||||
|
&proof.mleT,
|
||||
|
)?,
|
||||
|
vec![],
|
||||
|
))
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<C: CurveGroup, CS: CommitmentScheme<C, H>, T: Transcript<C::ScalarField>, const H: bool>
|
||||
|
NIFS<C, CS, T, H>
|
||||
|
{
|
||||
|
// Protocol 7 - point 3 (15)
|
||||
|
fn fold_committed_instance(
|
||||
|
a: C::ScalarField,
|
||||
|
U_i: &CommittedInstance<C>,
|
||||
|
u_i: &CommittedInstance<C>,
|
||||
|
rE_prime: &[C::ScalarField],
|
||||
|
mleE1_prime: &C::ScalarField,
|
||||
|
mleE2_prime: &C::ScalarField,
|
||||
|
mleT: &C::ScalarField,
|
||||
|
) -> Result<CommittedInstance<C>, Error> {
|
||||
|
let a2 = a * a;
|
||||
|
let mleE = *mleE1_prime + a * mleT + a2 * mleE2_prime;
|
||||
|
let u = U_i.u + a * u_i.u;
|
||||
|
let cmW = U_i.cmW + u_i.cmW.mul(a);
|
||||
|
let x = U_i
|
||||
|
.x
|
||||
|
.iter()
|
||||
|
.zip(&u_i.x)
|
||||
|
.map(|(i1, i2)| *i1 + (a * i2))
|
||||
|
.collect::<Vec<C::ScalarField>>();
|
||||
|
|
||||
|
Ok(CommittedInstance::<C> {
|
||||
|
rE: rE_prime.to_vec(),
|
||||
|
mleE,
|
||||
|
u,
|
||||
|
cmW,
|
||||
|
x,
|
||||
|
})
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
impl<C: CurveGroup> Arith<Witness<C>, CommittedInstance<C>> for R1CS<CF1<C>> {
|
||||
|
type Evaluation = Vec<CF1<C>>;
|
||||
|
|
||||
|
fn eval_relation(
|
||||
|
&self,
|
||||
|
w: &Witness<C>,
|
||||
|
u: &CommittedInstance<C>,
|
||||
|
) -> Result<Self::Evaluation, Error> {
|
||||
|
self.eval_at_z(&[&[u.u][..], &u.x, &w.W].concat())
|
||||
|
}
|
||||
|
|
||||
|
fn check_evaluation(
|
||||
|
w: &Witness<C>,
|
||||
|
_u: &CommittedInstance<C>,
|
||||
|
e: Self::Evaluation,
|
||||
|
) -> Result<(), Error> {
|
||||
|
(w.E == e).then_some(()).ok_or(Error::NotSatisfied)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[cfg(test)]
|
||||
|
pub mod tests {
|
||||
|
use super::*;
|
||||
|
use ark_crypto_primitives::sponge::poseidon::PoseidonSponge;
|
||||
|
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;
|
||||
|
|
||||
|
#[test]
|
||||
|
fn test_nifs_mova() {
|
||||
|
let (W, U) = test_nifs_opt::<NIFS<Projective, Pedersen<Projective>, PoseidonSponge<Fr>>>();
|
||||
|
|
||||
|
// check the last folded instance relation
|
||||
|
let r1cs = get_test_r1cs();
|
||||
|
r1cs.check_relation(&W, &U).unwrap();
|
||||
|
}
|
||||
|
}
|
@ -0,0 +1,282 @@ |
|||||
|
use ark_crypto_primitives::sponge::Absorb;
|
||||
|
use ark_ec::{CurveGroup, Group};
|
||||
|
use ark_ff::{One, PrimeField};
|
||||
|
use ark_poly::univariate::DensePolynomial;
|
||||
|
use ark_poly::{DenseMultilinearExtension, DenseUVPolynomial, Polynomial};
|
||||
|
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
|
||||
|
use ark_std::{log2, Zero};
|
||||
|
|
||||
|
use super::mova::{CommittedInstance, Witness};
|
||||
|
use crate::transcript::Transcript;
|
||||
|
use crate::utils::mle::dense_vec_to_dense_mle;
|
||||
|
use crate::Error;
|
||||
|
|
||||
|
/// Implements the Points vs Line as described in
|
||||
|
/// [Mova](https://eprint.iacr.org/2024/1220.pdf) and Section 4.5.2 from Thaler’s book
|
||||
|
|
||||
|
/// Claim from step 3 protocol 6
|
||||
|
pub struct PointvsLineEvaluationClaim<C: CurveGroup> {
|
||||
|
pub mleE1_prime: C::ScalarField,
|
||||
|
pub mleE2_prime: C::ScalarField,
|
||||
|
pub rE_prime: Vec<C::ScalarField>,
|
||||
|
}
|
||||
|
/// Proof from step 1 protocol 6
|
||||
|
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
|
||||
|
pub struct PointVsLineProof<C: CurveGroup> {
|
||||
|
pub h1: DensePolynomial<C::ScalarField>,
|
||||
|
pub h2: DensePolynomial<C::ScalarField>,
|
||||
|
}
|
||||
|
|
||||
|
#[derive(Clone, Debug, Default)]
|
||||
|
pub struct PointVsLine<C: CurveGroup, T: Transcript<C::ScalarField>> {
|
||||
|
_phantom_C: std::marker::PhantomData<C>,
|
||||
|
_phantom_T: std::marker::PhantomData<T>,
|
||||
|
}
|
||||
|
|
||||
|
/// Protocol 6 from Mova
|
||||
|
impl<C: CurveGroup, T: Transcript<C::ScalarField>> PointVsLine<C, T>
|
||||
|
where
|
||||
|
<C as Group>::ScalarField: Absorb,
|
||||
|
{
|
||||
|
pub fn prove(
|
||||
|
transcript: &mut T,
|
||||
|
ci1: &CommittedInstance<C>,
|
||||
|
ci2: &CommittedInstance<C>,
|
||||
|
w1: &Witness<C>,
|
||||
|
w2: &Witness<C>,
|
||||
|
) -> Result<(PointVsLineProof<C>, PointvsLineEvaluationClaim<C>), Error> {
|
||||
|
let n_vars: usize = log2(w1.E.len()) as usize;
|
||||
|
|
||||
|
let mleE1 = dense_vec_to_dense_mle(n_vars, &w1.E);
|
||||
|
let mleE2 = dense_vec_to_dense_mle(n_vars, &w2.E);
|
||||
|
|
||||
|
// We have l(0) = r1, l(1) = r2 so we know that l(x) = r1 + x(r2-r1) thats why we need r2-r1
|
||||
|
let r1_sub_r2: Vec<<C>::ScalarField> = ci1
|
||||
|
.rE
|
||||
|
.iter()
|
||||
|
.zip(&ci2.rE)
|
||||
|
.map(|(&r1, r2)| r1 - r2)
|
||||
|
.collect();
|
||||
|
|
||||
|
let h1 = compute_h(&mleE1, &ci1.rE, &r1_sub_r2)?;
|
||||
|
let h2 = compute_h(&mleE2, &ci1.rE, &r1_sub_r2)?;
|
||||
|
|
||||
|
transcript.absorb(&h1.coeffs());
|
||||
|
transcript.absorb(&h2.coeffs());
|
||||
|
|
||||
|
let beta_scalar = C::ScalarField::from_le_bytes_mod_order(b"beta");
|
||||
|
transcript.absorb(&beta_scalar);
|
||||
|
let beta = transcript.get_challenge();
|
||||
|
|
||||
|
let mleE1_prime = h1.evaluate(&beta);
|
||||
|
let mleE2_prime = h2.evaluate(&beta);
|
||||
|
|
||||
|
let rE_prime = compute_l(&ci1.rE, &r1_sub_r2, beta)?;
|
||||
|
|
||||
|
Ok((
|
||||
|
PointVsLineProof { h1, h2 },
|
||||
|
PointvsLineEvaluationClaim {
|
||||
|
mleE1_prime,
|
||||
|
mleE2_prime,
|
||||
|
rE_prime,
|
||||
|
},
|
||||
|
))
|
||||
|
}
|
||||
|
|
||||
|
pub fn verify(
|
||||
|
transcript: &mut T,
|
||||
|
ci1: &CommittedInstance<C>,
|
||||
|
ci2: &CommittedInstance<C>,
|
||||
|
proof: &PointVsLineProof<C>,
|
||||
|
mleE1_prime: &<C>::ScalarField,
|
||||
|
mleE2_prime: &<C>::ScalarField,
|
||||
|
) -> Result<
|
||||
|
Vec<<C>::ScalarField>, // rE=rE1'=rE2'.
|
||||
|
Error,
|
||||
|
> {
|
||||
|
if proof.h1.evaluate(&C::ScalarField::zero()) != ci1.mleE {
|
||||
|
return Err(Error::NotEqual);
|
||||
|
}
|
||||
|
|
||||
|
if proof.h2.evaluate(&C::ScalarField::one()) != ci2.mleE {
|
||||
|
return Err(Error::NotEqual);
|
||||
|
}
|
||||
|
|
||||
|
transcript.absorb(&proof.h1.coeffs());
|
||||
|
transcript.absorb(&proof.h2.coeffs());
|
||||
|
|
||||
|
let beta_scalar = C::ScalarField::from_le_bytes_mod_order(b"beta");
|
||||
|
transcript.absorb(&beta_scalar);
|
||||
|
let beta = transcript.get_challenge();
|
||||
|
|
||||
|
if *mleE1_prime != proof.h1.evaluate(&beta) {
|
||||
|
return Err(Error::NotEqual);
|
||||
|
}
|
||||
|
|
||||
|
if *mleE2_prime != proof.h2.evaluate(&beta) {
|
||||
|
return Err(Error::NotEqual);
|
||||
|
}
|
||||
|
|
||||
|
let r1_sub_r2: Vec<<C>::ScalarField> = ci1
|
||||
|
.rE
|
||||
|
.iter()
|
||||
|
.zip(&ci2.rE)
|
||||
|
.map(|(&r1, r2)| r1 - r2)
|
||||
|
.collect();
|
||||
|
let rE_prime = compute_l(&ci1.rE, &r1_sub_r2, beta)?;
|
||||
|
|
||||
|
Ok(rE_prime)
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
fn compute_h<F: PrimeField>(
|
||||
|
mle: &DenseMultilinearExtension<F>,
|
||||
|
r1: &[F],
|
||||
|
r1_sub_r2: &[F],
|
||||
|
) -> Result<DensePolynomial<F>, Error> {
|
||||
|
let n_vars: usize = mle.num_vars;
|
||||
|
if r1.len() != r1_sub_r2.len() || r1.len() != n_vars {
|
||||
|
return Err(Error::NotEqual);
|
||||
|
}
|
||||
|
|
||||
|
// Initialize the polynomial vector from the evaluations in the multilinear extension.
|
||||
|
// Each evaluation is turned into a constant polynomial.
|
||||
|
let mut poly: Vec<DensePolynomial<F>> = mle
|
||||
|
.evaluations
|
||||
|
.iter()
|
||||
|
.map(|&x| DensePolynomial::from_coefficients_slice(&[x]))
|
||||
|
.collect();
|
||||
|
|
||||
|
for (i, (&r1_i, &r1_sub_r2_i)) in r1.iter().zip(r1_sub_r2.iter()).enumerate().take(n_vars) {
|
||||
|
// Create a linear polynomial r(X) = r1_i + (r1_sub_r2_i) * X (basically l)
|
||||
|
let r = DensePolynomial::from_coefficients_slice(&[r1_i, r1_sub_r2_i]);
|
||||
|
let half_len = 1 << (n_vars - i - 1);
|
||||
|
|
||||
|
for b in 0..half_len {
|
||||
|
let left = &poly[b << 1];
|
||||
|
let right = &poly[(b << 1) + 1];
|
||||
|
poly[b] = left + &(&r * &(right - left));
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
// After the loop, we should be left with a single polynomial, so return it.
|
||||
|
Ok(poly.swap_remove(0))
|
||||
|
}
|
||||
|
|
||||
|
fn compute_l<F: PrimeField>(r1: &[F], r1_sub_r2: &[F], x: F) -> Result<Vec<F>, Error> {
|
||||
|
if r1.len() != r1_sub_r2.len() {
|
||||
|
return Err(Error::NotEqual);
|
||||
|
}
|
||||
|
|
||||
|
// we have l(x) = r1 + x(r2-r1) so return the result
|
||||
|
Ok(r1
|
||||
|
.iter()
|
||||
|
.zip(r1_sub_r2)
|
||||
|
.map(|(&r1, &r1_sub_r0)| r1 + x * r1_sub_r0)
|
||||
|
.collect())
|
||||
|
}
|
||||
|
|
||||
|
#[cfg(test)]
|
||||
|
mod tests {
|
||||
|
use super::{compute_h, compute_l};
|
||||
|
use ark_pallas::Fq;
|
||||
|
use ark_poly::{DenseMultilinearExtension, DenseUVPolynomial};
|
||||
|
|
||||
|
#[test]
|
||||
|
fn test_compute_h() {
|
||||
|
let mle = DenseMultilinearExtension::from_evaluations_slice(1, &[Fq::from(1), Fq::from(2)]);
|
||||
|
let r0 = [Fq::from(5)];
|
||||
|
let r1 = [Fq::from(6)];
|
||||
|
let r1_sub_r0: Vec<Fq> = r1.iter().zip(&r0).map(|(&x, y)| x - y).collect();
|
||||
|
|
||||
|
let result = compute_h(&mle, &r0, &r1_sub_r0).unwrap();
|
||||
|
assert_eq!(
|
||||
|
result,
|
||||
|
DenseUVPolynomial::from_coefficients_slice(&[Fq::from(6), Fq::from(1)])
|
||||
|
);
|
||||
|
|
||||
|
let mle = DenseMultilinearExtension::from_evaluations_slice(1, &[Fq::from(1), Fq::from(2)]);
|
||||
|
let r0 = [Fq::from(4)];
|
||||
|
let r1 = [Fq::from(7)];
|
||||
|
let r1_sub_r0: Vec<Fq> = r1.iter().zip(&r0).map(|(&x, y)| x - y).collect();
|
||||
|
|
||||
|
let result = compute_h(&mle, &r0, &r1_sub_r0).unwrap();
|
||||
|
assert_eq!(
|
||||
|
result,
|
||||
|
DenseUVPolynomial::from_coefficients_slice(&[Fq::from(5), Fq::from(3)])
|
||||
|
);
|
||||
|
|
||||
|
let mle = DenseMultilinearExtension::from_evaluations_slice(
|
||||
|
2,
|
||||
|
&[Fq::from(1), Fq::from(2), Fq::from(3), Fq::from(4)],
|
||||
|
);
|
||||
|
let r0 = [Fq::from(5), Fq::from(4)];
|
||||
|
let r1 = [Fq::from(2), Fq::from(7)];
|
||||
|
let r1_sub_r0: Vec<Fq> = r1.iter().zip(&r0).map(|(&x, y)| x - y).collect();
|
||||
|
|
||||
|
let result = compute_h(&mle, &r0, &r1_sub_r0).unwrap();
|
||||
|
assert_eq!(
|
||||
|
result,
|
||||
|
DenseUVPolynomial::from_coefficients_slice(&[Fq::from(14), Fq::from(3)])
|
||||
|
);
|
||||
|
let mle = DenseMultilinearExtension::from_evaluations_slice(
|
||||
|
3,
|
||||
|
&[
|
||||
|
Fq::from(1),
|
||||
|
Fq::from(2),
|
||||
|
Fq::from(3),
|
||||
|
Fq::from(4),
|
||||
|
Fq::from(5),
|
||||
|
Fq::from(6),
|
||||
|
Fq::from(7),
|
||||
|
Fq::from(8),
|
||||
|
],
|
||||
|
);
|
||||
|
let r0 = [Fq::from(1), Fq::from(2), Fq::from(3)];
|
||||
|
let r1 = [Fq::from(5), Fq::from(6), Fq::from(7)];
|
||||
|
let r1_sub_r0: Vec<Fq> = r1.iter().zip(&r0).map(|(&x, y)| x - y).collect();
|
||||
|
|
||||
|
let result = compute_h(&mle, &r0, &r1_sub_r0).unwrap();
|
||||
|
assert_eq!(
|
||||
|
result,
|
||||
|
DenseUVPolynomial::from_coefficients_slice(&[Fq::from(18), Fq::from(28)])
|
||||
|
);
|
||||
|
}
|
||||
|
|
||||
|
#[test]
|
||||
|
fn test_compute_h_errors() {
|
||||
|
let mle = DenseMultilinearExtension::from_evaluations_slice(1, &[Fq::from(1), Fq::from(2)]);
|
||||
|
let r0 = [Fq::from(5)];
|
||||
|
let r1_sub_r0 = [];
|
||||
|
let result = compute_h(&mle, &r0, &r1_sub_r0);
|
||||
|
assert!(result.is_err());
|
||||
|
|
||||
|
let mle = DenseMultilinearExtension::from_evaluations_slice(
|
||||
|
2,
|
||||
|
&[Fq::from(1), Fq::from(2), Fq::from(1), Fq::from(2)],
|
||||
|
);
|
||||
|
let r0 = [Fq::from(4)];
|
||||
|
let r1 = [Fq::from(7)];
|
||||
|
let r1_sub_r0: Vec<Fq> = r1.iter().zip(&r0).map(|(&x, y)| x - y).collect();
|
||||
|
|
||||
|
let result = compute_h(&mle, &r0, &r1_sub_r0);
|
||||
|
assert!(result.is_err())
|
||||
|
}
|
||||
|
|
||||
|
#[test]
|
||||
|
fn test_compute_l() {
|
||||
|
// Test with simple non-zero values
|
||||
|
let r1 = vec![Fq::from(1), Fq::from(2), Fq::from(3)];
|
||||
|
let r1_sub_r2 = vec![Fq::from(4), Fq::from(5), Fq::from(6)];
|
||||
|
let x = Fq::from(2);
|
||||
|
|
||||
|
let expected = vec![
|
||||
|
Fq::from(1) + Fq::from(2) * Fq::from(4),
|
||||
|
Fq::from(2) + Fq::from(2) * Fq::from(5),
|
||||
|
Fq::from(3) + Fq::from(2) * Fq::from(6),
|
||||
|
];
|
||||
|
|
||||
|
let result = compute_l(&r1, &r1_sub_r2, x).unwrap();
|
||||
|
assert_eq!(result, expected);
|
||||
|
}
|
||||
|
}
|