#![allow(non_snake_case)]
use crate::{
constants::NUM_CHALLENGE_BITS,
errors::NovaError,
neutron::relation::{FoldedInstance, FoldedWitness, Structure},
r1cs::{R1CSInstance, R1CSWitness},
spartan::polys::{power::PowPolynomial, univariate::UniPoly},
traits::{commitment::CommitmentEngineTrait, AbsorbInRO2Trait, Engine, RO2Constants, ROTrait},
Commitment, CommitmentKey, CE,
};
use ff::Field;
use rand_core::OsRng;
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
#[allow(clippy::upper_case_acronyms)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(bound = "")]
pub struct NIFS<E: Engine> {
pub(crate) comm_E: Commitment<E>,
pub(crate) poly: UniPoly<E::Scalar>,
}
impl<E: Engine> NIFS<E> {
#[inline]
fn prove_helper(
rho: &E::Scalar,
(left, right): (usize, usize),
e1: &[E::Scalar],
Az1: &[E::Scalar],
Bz1: &[E::Scalar],
Cz1: &[E::Scalar],
e2: &[E::Scalar],
Az2: &[E::Scalar],
Bz2: &[E::Scalar],
Cz2: &[E::Scalar],
) -> (E::Scalar, E::Scalar, E::Scalar, E::Scalar, E::Scalar) {
assert_eq!(e1.len(), left + right);
assert_eq!(Az1.len(), left * right);
assert_eq!(Bz1.len(), left * right);
assert_eq!(Cz1.len(), left * right);
assert_eq!(e2.len(), left + right);
assert_eq!(Az2.len(), left * right);
assert_eq!(Bz2.len(), left * right);
assert_eq!(Cz2.len(), left * right);
let comb_func = |c1: &E::Scalar, c2: &E::Scalar, c3: &E::Scalar, c4: &E::Scalar| -> E::Scalar {
*c1 * (*c2 * *c3 - *c4)
};
let (eval_at_0, eval_at_2, eval_at_3, eval_at_4, eval_at_5) = (0..right)
.into_par_iter()
.map(|i| {
let (i_eval_at_0, i_eval_at_2, i_eval_at_3, i_eval_at_4, i_eval_at_5) = (0..left)
.into_par_iter()
.map(|j| {
let k = i * left + j;
let eval_point_0 = comb_func(&e1[j], &Az1[k], &Bz1[k], &Cz1[k]);
let poly_e_bound_point = e2[j] + e2[j] - e1[j];
let poly_Az_bound_point = Az2[k] + Az2[k] - Az1[k];
let poly_Bz_bound_point = Bz2[k] + Bz2[k] - Bz1[k];
let poly_Cz_bound_point = Cz2[k] + Cz2[k] - Cz1[k];
let eval_point_2 = comb_func(
&poly_e_bound_point,
&poly_Az_bound_point,
&poly_Bz_bound_point,
&poly_Cz_bound_point,
);
let poly_e_bound_point = poly_e_bound_point + e2[j] - e1[j];
let poly_Az_bound_point = poly_Az_bound_point + Az2[k] - Az1[k];
let poly_Bz_bound_point = poly_Bz_bound_point + Bz2[k] - Bz1[k];
let poly_Cz_bound_point = poly_Cz_bound_point + Cz2[k] - Cz1[k];
let eval_point_3 = comb_func(
&poly_e_bound_point,
&poly_Az_bound_point,
&poly_Bz_bound_point,
&poly_Cz_bound_point,
);
let poly_e_bound_point = poly_e_bound_point + e2[j] - e1[j];
let poly_Az_bound_point = poly_Az_bound_point + Az2[k] - Az1[k];
let poly_Bz_bound_point = poly_Bz_bound_point + Bz2[k] - Bz1[k];
let poly_Cz_bound_point = poly_Cz_bound_point + Cz2[k] - Cz1[k];
let eval_point_4 = comb_func(
&poly_e_bound_point,
&poly_Az_bound_point,
&poly_Bz_bound_point,
&poly_Cz_bound_point,
);
let poly_e_bound_point = poly_e_bound_point + e2[j] - e1[j];
let poly_Az_bound_point = poly_Az_bound_point + Az2[k] - Az1[k];
let poly_Bz_bound_point = poly_Bz_bound_point + Bz2[k] - Bz1[k];
let poly_Cz_bound_point = poly_Cz_bound_point + Cz2[k] - Cz1[k];
let eval_point_5 = comb_func(
&poly_e_bound_point,
&poly_Az_bound_point,
&poly_Bz_bound_point,
&poly_Cz_bound_point,
);
(
eval_point_0,
eval_point_2,
eval_point_3,
eval_point_4,
eval_point_5,
)
})
.reduce(
|| {
(
E::Scalar::ZERO,
E::Scalar::ZERO,
E::Scalar::ZERO,
E::Scalar::ZERO,
E::Scalar::ZERO,
)
},
|a, b| (a.0 + b.0, a.1 + b.1, a.2 + b.2, a.3 + b.3, a.4 + b.4),
);
let f1 = &e1[left..];
let f2 = &e2[left..];
let eval_at_0 = f1[i] * i_eval_at_0;
let poly_f_bound_point = f2[i] + f2[i] - f1[i];
let eval_at_2 = poly_f_bound_point * i_eval_at_2;
let poly_f_bound_point = poly_f_bound_point + f2[i] - f1[i];
let eval_at_3 = poly_f_bound_point * i_eval_at_3;
let poly_f_bound_point = poly_f_bound_point + f2[i] - f1[i];
let eval_at_4 = poly_f_bound_point * i_eval_at_4;
let poly_f_bound_point = poly_f_bound_point + f2[i] - f1[i];
let eval_at_5 = poly_f_bound_point * i_eval_at_5;
(eval_at_0, eval_at_2, eval_at_3, eval_at_4, eval_at_5)
})
.reduce(
|| {
(
E::Scalar::ZERO,
E::Scalar::ZERO,
E::Scalar::ZERO,
E::Scalar::ZERO,
E::Scalar::ZERO,
)
},
|a, b| (a.0 + b.0, a.1 + b.1, a.2 + b.2, a.3 + b.3, a.4 + b.4),
);
let one_minus_rho = E::Scalar::ONE - rho;
let three_rho_minus_one = E::Scalar::from(3) * rho - E::Scalar::ONE;
let five_rho_minus_two = E::Scalar::from(5) * rho - E::Scalar::from(2);
let seven_rho_minus_three = E::Scalar::from(7) * rho - E::Scalar::from(3);
let nine_rho_minus_four = E::Scalar::from(9) * rho - E::Scalar::from(4);
(
eval_at_0 * one_minus_rho,
eval_at_2 * three_rho_minus_one,
eval_at_3 * five_rho_minus_two,
eval_at_4 * seven_rho_minus_three,
eval_at_5 * nine_rho_minus_four,
)
}
pub fn prove(
ck: &CommitmentKey<E>,
ro_consts: &RO2Constants<E>,
pp_digest: &E::Scalar,
S: &Structure<E>,
U1: &FoldedInstance<E>,
W1: &FoldedWitness<E>,
U2: &R1CSInstance<E>,
W2: &R1CSWitness<E>,
) -> Result<(NIFS<E>, (FoldedInstance<E>, FoldedWitness<E>)), NovaError> {
let mut ro = E::RO2::new(ro_consts.clone());
ro.absorb(*pp_digest);
U2.absorb_in_ro2(&mut ro);
let tau = ro.squeeze(NUM_CHALLENGE_BITS, false);
let E = PowPolynomial::new(&tau, S.ell).split_evals(S.left, S.right);
let r_E = E::Scalar::random(&mut OsRng);
let comm_E = CE::<E>::commit(ck, &E, &r_E);
comm_E.absorb_in_ro2(&mut ro);
let rho = ro.squeeze(NUM_CHALLENGE_BITS, false);
let T = (E::Scalar::ONE - rho) * U1.T;
let (res1, res2) = rayon::join(
|| {
let z1 = [W1.W.clone(), vec![U1.u], U1.X.clone()].concat();
S.S.multiply_vec(&z1)
},
|| {
let z2 = [W2.W.clone(), vec![E::Scalar::ONE], U2.X.clone()].concat();
S.S.multiply_vec(&z2)
},
);
let (Az1, Bz1, Cz1) = res1?;
let (Az2, Bz2, Cz2) = res2?;
let (eval_point_0, eval_point_2, eval_point_3, eval_point_4, eval_point_5) = Self::prove_helper(
&rho,
(S.left, S.right),
&W1.E,
&Az1,
&Bz1,
&Cz1,
&E,
&Az2,
&Bz2,
&Cz2,
);
let evals = vec![
eval_point_0,
T - eval_point_0,
eval_point_2,
eval_point_3,
eval_point_4,
eval_point_5,
];
let poly = UniPoly::<E::Scalar>::from_evals(&evals);
<UniPoly<E::Scalar> as AbsorbInRO2Trait<E>>::absorb_in_ro2(&poly, &mut ro);
let r_b = ro.squeeze(NUM_CHALLENGE_BITS, false);
let eq_rho_r_b = (E::Scalar::ONE - rho) * (E::Scalar::ONE - r_b) + rho * r_b;
let T_out = poly.evaluate(&r_b) * eq_rho_r_b.invert().unwrap();
let U = U1.fold(U2, &comm_E, &r_b, &T_out)?;
let W = W1.fold(W2, &E, &r_E, &r_b)?;
Ok((Self { comm_E, poly }, (U, W)))
}
#[cfg(test)]
pub fn verify(
&self,
ro_consts: &RO2Constants<E>,
pp_digest: &E::Scalar,
U1: &FoldedInstance<E>,
U2: &R1CSInstance<E>,
) -> Result<FoldedInstance<E>, NovaError> {
let mut ro = E::RO2::new(ro_consts.clone());
ro.absorb(*pp_digest);
U2.absorb_in_ro2(&mut ro);
let _tau = ro.squeeze(NUM_CHALLENGE_BITS, false);
self.comm_E.absorb_in_ro2(&mut ro);
let rho = ro.squeeze(NUM_CHALLENGE_BITS, false);
let T = (E::Scalar::ONE - rho) * U1.T;
if self.poly.eval_at_zero() + self.poly.eval_at_one() != T {
return Err(NovaError::InvalidSumcheckProof);
}
<UniPoly<E::Scalar> as AbsorbInRO2Trait<E>>::absorb_in_ro2(&self.poly, &mut ro);
let r_b = ro.squeeze(NUM_CHALLENGE_BITS, false);
let eq_rho_r_b = (E::Scalar::ONE - rho) * (E::Scalar::ONE - r_b) + rho * r_b;
let T_out = self.poly.evaluate(&r_b) * eq_rho_r_b.invert().unwrap();
let U = U1.fold(U2, &self.comm_E, &r_b, &T_out)?;
Ok(U)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
frontend::{
r1cs::{NovaShape, NovaWitness},
shape_cs::ShapeCS,
solver::SatisfyingAssignment,
Circuit, ConstraintSystem,
},
provider::{
hyperkzg::EvaluationEngine as HyperKZGEE, ipa_pc::EvaluationEngine, Bn256EngineKZG,
PallasEngine, Secp256k1Engine,
},
r1cs::R1CSShape,
spartan::{direct::DirectCircuit, snark::RelaxedR1CSSNARK},
traits::{circuit::NonTrivialCircuit, snark::RelaxedR1CSSNARKTrait, Engine, RO2Constants},
};
use ff::Field;
fn execute_sequence<E: Engine>(
ck: &CommitmentKey<E>,
ro_consts: &RO2Constants<E>,
pp_digest: &<E as Engine>::Scalar,
shape: &R1CSShape<E>,
U1: &R1CSInstance<E>,
W1: &R1CSWitness<E>,
U2: &R1CSInstance<E>,
W2: &R1CSWitness<E>,
) {
let str = Structure::new(shape);
let mut running_W = FoldedWitness::default(&str);
let mut running_U = FoldedInstance::default(&str);
let res = str.is_sat(ck, &running_U, &running_W);
if res != Ok(()) {
println!("Error: {:?}", res);
}
assert!(res.is_ok());
let res = NIFS::prove(
ck, ro_consts, pp_digest, &str, &running_U, &running_W, U1, W1,
);
assert!(res.is_ok());
let (nifs, (_U, W)) = res.unwrap();
let res = nifs.verify(ro_consts, pp_digest, &running_U, U1);
assert!(res.is_ok());
let U = res.unwrap();
assert_eq!(U, _U);
running_W = W;
running_U = U;
let res = str.is_sat(ck, &running_U, &running_W);
if res != Ok(()) {
println!("Error: {:?}", res);
}
assert!(res.is_ok());
let res = NIFS::prove(
ck, ro_consts, pp_digest, &str, &running_U, &running_W, U2, W2,
);
assert!(res.is_ok());
let (nifs, (_U, W)) = res.unwrap();
let res = nifs.verify(ro_consts, pp_digest, &running_U, U2);
assert!(res.is_ok());
let U = res.unwrap();
assert_eq!(U, _U);
running_W = W;
running_U = U;
let res = str.is_sat(ck, &running_U, &running_W);
if res != Ok(()) {
println!("Error: {:?}", res);
}
assert!(res.is_ok());
}
fn test_tiny_r1cs_bellpepper_with<E: Engine, S: RelaxedR1CSSNARKTrait<E>>() {
let ro_consts = RO2Constants::<E>::default();
let num_cons: usize = 32;
let circuit: DirectCircuit<E, NonTrivialCircuit<E::Scalar>> =
DirectCircuit::new(None, NonTrivialCircuit::<E::Scalar>::new(num_cons));
let mut cs: ShapeCS<E> = ShapeCS::new();
let _ = circuit.synthesize(&mut cs);
let shape = cs.r1cs_shape().unwrap();
let ck = R1CSShape::commitment_key(&[&shape], &[&*S::ck_floor()]).unwrap();
let circuit: DirectCircuit<E, NonTrivialCircuit<E::Scalar>> = DirectCircuit::new(
Some(vec![E::Scalar::from(2)]),
NonTrivialCircuit::<E::Scalar>::new(num_cons),
);
let mut cs = SatisfyingAssignment::<E>::new();
let _ = circuit.synthesize(&mut cs);
let (U1, W1) = cs
.r1cs_instance_and_witness(&shape, &ck)
.map_err(|_e| NovaError::UnSat {
reason: "Unable to generate a satisfying witness".to_string(),
})
.unwrap();
let circuit: DirectCircuit<E, NonTrivialCircuit<E::Scalar>> = DirectCircuit::new(
Some(vec![E::Scalar::from(3)]),
NonTrivialCircuit::<E::Scalar>::new(num_cons),
);
let mut cs = SatisfyingAssignment::<E>::new();
let _ = circuit.synthesize(&mut cs);
let (U2, W2) = cs
.r1cs_instance_and_witness(&shape, &ck)
.map_err(|_e| NovaError::UnSat {
reason: "Unable to generate a satisfying witness".to_string(),
})
.unwrap();
let shape = shape.pad();
let W1 = W1.pad(&shape);
let W2 = W2.pad(&shape);
execute_sequence(
&ck,
&ro_consts,
&<E as Engine>::Scalar::ZERO,
&shape,
&U1,
&W1,
&U2,
&W2,
);
}
#[test]
fn test_tiny_r1cs_bellpepper() {
test_tiny_r1cs_bellpepper_with::<PallasEngine, RelaxedR1CSSNARK<_, EvaluationEngine<_>>>();
test_tiny_r1cs_bellpepper_with::<Bn256EngineKZG, RelaxedR1CSSNARK<_, HyperKZGEE<_>>>();
test_tiny_r1cs_bellpepper_with::<Secp256k1Engine, RelaxedR1CSSNARK<_, EvaluationEngine<_>>>();
}
}
#[cfg(test)]
mod benchmarks {
use super::*;
use crate::{
frontend::{
gadgets::{
boolean::{AllocatedBit, Boolean},
num::AllocatedNum,
sha256::sha256,
},
r1cs::{NovaShape, NovaWitness},
shape_cs::ShapeCS,
solver::SatisfyingAssignment,
ConstraintSystem, SynthesisError,
},
nova::nifs::NIFS as NovaNIFS,
provider::Bn256EngineKZG,
r1cs::{R1CSShape, SparseMatrix},
traits::{snark::default_ck_hint, ROConstants},
};
use core::marker::PhantomData;
use criterion::Criterion;
use ff::PrimeField;
use num_integer::Integer;
use num_traits::ToPrimitive;
use rand::Rng;
fn generate_sample_r1cs<E: Engine>(
num_cons: usize,
) -> (
R1CSShape<E>,
CommitmentKey<E>,
R1CSWitness<E>,
Vec<u8>,
Vec<E::Scalar>,
) {
let num_vars = num_cons;
let num_io = 1;
let A = SparseMatrix::new(
&(0..num_cons)
.map(|i| (i, i, E::Scalar::ONE))
.collect::<Vec<_>>(),
num_cons,
num_vars + 1 + num_io,
);
let B = A.clone();
let C = A.clone();
let S: R1CSShape<E> = R1CSShape::new(num_cons, num_vars, num_io, A, B, C).unwrap();
let S = S.pad();
let ck = R1CSShape::commitment_key(&[&S], &[&*default_ck_hint()]).unwrap();
let w = (0..S.num_cons)
.into_par_iter()
.map(|_| {
let mut rng = rand::thread_rng();
rng.gen::<u8>() % 2
})
.collect::<Vec<_>>();
let W = {
let W = (0..S.num_cons)
.into_par_iter()
.map(|i| <E as Engine>::Scalar::from(w[i] as u64))
.collect::<Vec<_>>();
R1CSWitness::new(&S, &W).unwrap()
};
let x = vec![E::Scalar::from(0)];
(S, ck, W, w, x)
}
struct Sha256Circuit<E: Engine> {
preimage: Vec<u8>,
_p: PhantomData<E>,
}
impl<E: Engine> Sha256Circuit<E> {
pub fn synthesize<CS: ConstraintSystem<E::Scalar>>(
&self,
cs: &mut CS,
) -> Result<(), SynthesisError> {
let bit_values: Vec<_> = self
.preimage
.clone()
.into_iter()
.flat_map(|byte| (0..8).map(move |i| (byte >> i) & 1u8 == 1u8))
.map(Some)
.collect();
assert_eq!(bit_values.len(), self.preimage.len() * 8);
let preimage_bits = bit_values
.into_iter()
.enumerate()
.map(|(i, b)| AllocatedBit::alloc(cs.namespace(|| format!("preimage bit {i}")), b))
.map(|b| b.map(Boolean::from))
.collect::<Result<Vec<_>, _>>()?;
let _ = sha256(cs.namespace(|| "sha256"), &preimage_bits)?;
let x = AllocatedNum::alloc(cs.namespace(|| "x"), || Ok(E::Scalar::ZERO))?;
x.inputize(cs.namespace(|| "inputize x"))?;
Ok(())
}
}
fn generarate_sha_r1cs<E: Engine>(
len: usize,
) -> (
R1CSShape<E>,
CommitmentKey<E>,
R1CSWitness<E>,
Vec<u8>,
Vec<E::Scalar>,
) {
let circuit = Sha256Circuit::<E> {
preimage: vec![0u8; len],
_p: Default::default(),
};
let mut cs: ShapeCS<E> = ShapeCS::new();
let _ = circuit.synthesize(&mut cs);
let S = cs.r1cs_shape().unwrap();
let ck = R1CSShape::commitment_key(&[&S], &[&*default_ck_hint()]).unwrap();
let mut cs = SatisfyingAssignment::<E>::new();
let _ = circuit.synthesize(&mut cs);
let (U, W) = cs.r1cs_instance_and_witness(&S, &ck).unwrap();
let S = S.pad();
let W = W.pad(&S);
let w = W
.W
.iter()
.map(|e| {
e.to_repr().as_ref()[0]
})
.collect::<Vec<_>>();
let comm_W = <E as Engine>::CE::commit_small(&ck, &w, &W.r_W);
assert_eq!(comm_W, U.comm_W);
let X = U.X.clone();
(S, ck, W, w, X)
}
fn bench_nifs_inner<E: Engine, T: Integer + Into<u64> + Copy + Sync + ToPrimitive>(
c: &mut Criterion,
name: &str,
S: &R1CSShape<E>,
ck: &CommitmentKey<E>,
W: &R1CSWitness<E>,
w: &[T],
x: &[E::Scalar],
) {
let num_cons = S.num_cons;
let str = Structure::new(S);
let f_W = FoldedWitness::default(&str);
let f_U = FoldedInstance::default(&str);
let res = str.is_sat(ck, &f_U, &f_W);
assert!(res.is_ok());
let pp_digest = E::Scalar::ZERO;
let ro_consts = RO2Constants::<E>::default();
c.bench_function(&format!("neutron_nifs_{name}_{num_cons}"), |b| {
b.iter(|| {
let comm_W = E::CE::commit_small(ck, w, &W.r_W);
let U = R1CSInstance::new(S, &comm_W, x).unwrap();
let res = NIFS::prove(ck, &ro_consts, &pp_digest, &str, &f_U, &f_W, &U, W);
assert!(res.is_ok());
})
});
let (r_U, r_W) = R1CSShape::<E>::sample_random_instance_witness(S, ck).unwrap();
let ro_consts = ROConstants::<E>::default();
c.bench_function(&format!("nova_nifs_{name}_{num_cons}"), |b| {
b.iter(|| {
let comm_W = W.commit(ck);
let U = R1CSInstance::new(S, &comm_W, x).unwrap();
let res = NovaNIFS::prove(ck, &ro_consts, &pp_digest, S, &r_U, &r_W, &U, W);
assert!(res.is_ok());
})
});
}
#[test]
fn bench_nifs_simple() {
type E = Bn256EngineKZG;
let mut criterion = Criterion::default();
let num_cons = 1024;
let (S, ck, W, w, x) = generate_sample_r1cs::<E>(num_cons); bench_nifs_inner(&mut criterion, "simple", &S, &ck, &W, &w, &x);
}
#[test]
fn bench_nifs_sha256() {
type E = Bn256EngineKZG;
let mut criterion = Criterion::default();
for len in [32, 64].iter() {
let (S, ck, W, w, x) = generarate_sha_r1cs::<E>(*len);
bench_nifs_inner(&mut criterion, "sha256", &S, &ck, &W, &w, &x);
}
}
}