use crate::{
CommitmentKey,
bellpepper::{
r1cs::{
MultiRoundSpartanShape, MultiRoundSpartanWitness, PrecommittedState, RerandomizationTrait,
SpartanShape, SpartanWitness,
},
shape_cs::ShapeCS,
solver::SatisfyingAssignment,
},
digest::{DigestComputer, SimpleDigestible},
errors::SpartanError,
math::Math,
nifs::NovaNIFS,
polys::{
eq::EqPolynomial,
multilinear::{MultilinearPolynomial, SparsePolynomial},
power::PowPolynomial,
univariate::UniPoly,
},
r1cs::{
R1CSInstance, R1CSShape, R1CSWitness, RelaxedR1CSInstance, RelaxedR1CSWitness,
SplitMultiRoundR1CSInstance, SplitMultiRoundR1CSShape, SplitR1CSInstance, SplitR1CSShape,
},
start_span,
sumcheck::SumcheckProof,
traits::{
Engine,
circuit::SpartanCircuit,
pcs::{FoldingEngineTrait, PCSEngineTrait},
snark::{DigestHelperTrait, SpartanDigest},
transcript::TranscriptEngineTrait,
},
zk::NeutronNovaVerifierCircuit,
};
use ff::Field;
use once_cell::sync::OnceCell;
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use tracing::{debug, info};
fn compute_tensor_decomp(n: usize) -> (usize, usize, usize) {
let ell = n.next_power_of_two().log_2();
let ell1 = ell.div_ceil(2); let ell2 = ell / 2;
let left = 1 << ell1;
let right = 1 << ell2;
(ell, left, right)
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(bound = "")]
pub struct NeutronNovaNIFS<E: Engine> {
polys: Vec<UniPoly<E::Scalar>>,
}
#[inline]
#[allow(clippy::needless_range_loop)]
fn suffix_weight_full<F: Field>(t: usize, ell_b: usize, pair_idx: usize, rhos: &[F]) -> F {
let mut w = F::ONE;
let mut k = pair_idx;
for s in (t + 1)..ell_b {
let bit = (k & 1) as u8; w *= if bit == 0 { F::ONE - rhos[s] } else { rhos[s] };
k >>= 1;
}
w
}
impl<E: Engine> NeutronNovaNIFS<E>
where
E::PCS: FoldingEngineTrait<E>,
{
#[inline]
fn prove_helper(
rho: &E::Scalar,
(left, right): (usize, usize),
e: &[E::Scalar],
Az1: &[E::Scalar],
Bz1: &[E::Scalar],
Cz1: &[E::Scalar],
Az2: &[E::Scalar],
Bz2: &[E::Scalar],
Cz2: &[E::Scalar],
) -> (E::Scalar, E::Scalar, E::Scalar) {
assert_eq!(e.len(), left + right);
assert_eq!(Az1.len(), left * right);
assert_eq!(Bz1.len(), left * right);
assert_eq!(Cz1.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) = (0..right)
.into_par_iter()
.map(|i| {
let (i_eval_at_0, i_eval_at_2, i_eval_at_3) = (0..left)
.into_par_iter()
.map(|j| {
let k = i * left + j;
let poly_e_bound_point = e[j];
let eval_point_0 = comb_func(&poly_e_bound_point, &Az1[k], &Bz1[k], &Cz1[k]);
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_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,
);
(eval_point_0, eval_point_2, eval_point_3)
})
.reduce(
|| (E::Scalar::ZERO, E::Scalar::ZERO, E::Scalar::ZERO),
|a, b| (a.0 + b.0, a.1 + b.1, a.2 + b.2),
);
let f = &e[left..];
let poly_f_bound_point = f[i];
let eval_at_0 = poly_f_bound_point * i_eval_at_0;
let eval_at_2 = poly_f_bound_point * i_eval_at_2;
let eval_at_3 = poly_f_bound_point * i_eval_at_3;
(eval_at_0, eval_at_2, eval_at_3)
})
.reduce(
|| (E::Scalar::ZERO, E::Scalar::ZERO, E::Scalar::ZERO),
|a, b| (a.0 + b.0, a.1 + b.1, a.2 + b.2),
);
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);
(
eval_at_0 * one_minus_rho,
eval_at_2 * three_rho_minus_one,
eval_at_3 * five_rho_minus_two,
)
}
pub fn prove(
S: &SplitR1CSShape<E>,
Us: &[R1CSInstance<E>],
Ws: &[R1CSWitness<E>],
vc: &mut NeutronNovaVerifierCircuit<E>,
vc_state: &mut <SatisfyingAssignment<E> as MultiRoundSpartanWitness<E>>::MultiRoundState,
vc_shape: &SplitMultiRoundR1CSShape<E>,
vc_ck: &CommitmentKey<E>,
transcript: &mut E::TE,
) -> Result<
(
Vec<E::Scalar>, // E_eq (split evals, length left+right)
Vec<E::Scalar>, // Az layer 0
Vec<E::Scalar>, // Bz layer 0
Vec<E::Scalar>, // Cz layer 0
R1CSWitness<E>, // final folded witness
R1CSInstance<E>, // final folded instance
),
SpartanError,
> {
let n = Us.len();
let n_padded = Us.len().next_power_of_two();
let ell_b = n_padded.log_2();
info!(
"NeutronNova NIFS prove for {} instances and padded to {} instances",
Us.len(),
n_padded
);
let mut Us = Us.to_vec();
let mut Ws = Ws.to_vec();
if Us.len() < n_padded {
Us.extend(vec![Us[0].clone(); n_padded - n]);
Ws.extend(vec![Ws[0].clone(); n_padded - n]);
}
for U in Us.iter() {
transcript.absorb(b"U", U);
}
let T = E::Scalar::ZERO;
transcript.absorb(b"T", &T);
let (ell_cons, left, right) = compute_tensor_decomp(S.num_cons);
let tau = transcript.squeeze(b"tau")?;
let E_eq = PowPolynomial::new(&tau, ell_cons).split_evals(left, right);
let mut rhos = Vec::with_capacity(ell_b);
for _ in 0..ell_b {
rhos.push(transcript.squeeze(b"rho")?);
}
let chunk_len = left * right;
let triples = (0..n_padded)
.into_par_iter()
.map(|i| {
let w = &Ws[i].W;
let x = &Us[i].X;
let mut z = Vec::with_capacity(w.len() + 1 + x.len());
z.extend_from_slice(w);
z.push(E::Scalar::ONE);
z.extend_from_slice(x);
S.multiply_vec(&z)
})
.collect::<Result<Vec<_>, _>>()?;
let mut A_layers: Vec<Vec<E::Scalar>> = Vec::with_capacity(n_padded);
let mut B_layers: Vec<Vec<E::Scalar>> = Vec::with_capacity(n_padded);
let mut C_layers: Vec<Vec<E::Scalar>> = Vec::with_capacity(n_padded);
for (a, b, c) in triples {
A_layers.push(a);
B_layers.push(b);
C_layers.push(c);
}
let mut polys: Vec<UniPoly<E::Scalar>> = Vec::with_capacity(ell_b);
let mut r_bs: Vec<E::Scalar> = Vec::with_capacity(ell_b);
let mut T_cur = E::Scalar::ZERO; let mut acc_eq = E::Scalar::ONE;
let mut m = n_padded;
for t in 0..ell_b {
let rho_t = rhos[t];
let pairs = m / 2;
let (e0, e2, e3) = (0..pairs)
.into_par_iter()
.map(|pair_idx| {
let lo = 2 * pair_idx;
let hi = lo + 1;
let (a0, a2, a3) = Self::prove_helper(
&rho_t,
(left, right),
&E_eq,
&A_layers[lo],
&B_layers[lo],
&C_layers[lo],
&A_layers[hi],
&B_layers[hi],
&C_layers[hi],
);
let w = suffix_weight_full::<E::Scalar>(t, ell_b, pair_idx, &rhos);
(a0 * w, a2 * w, a3 * w)
})
.reduce(
|| (E::Scalar::ZERO, E::Scalar::ZERO, E::Scalar::ZERO),
|a, b| (a.0 + b.0, a.1 + b.1, a.2 + b.2),
);
let se0 = acc_eq * e0;
let se2 = acc_eq * e2;
let se3 = acc_eq * e3;
let poly_t = UniPoly::<E::Scalar>::from_evals(&[se0, T_cur - se0, se2, se3])?;
polys.push(poly_t.clone());
let c = &poly_t.coeffs;
vc.nifs_polys[t] = [c[0], c[1], c[2], c[3]];
let chals =
SatisfyingAssignment::<E>::process_round(vc_state, vc_shape, vc_ck, vc, t, transcript)?;
let r_b = chals[0];
r_bs.push(r_b);
acc_eq *= (E::Scalar::ONE - r_b) * (E::Scalar::ONE - rho_t) + r_b * rho_t;
T_cur = poly_t.evaluate(&r_b);
let mut next_A: Vec<Vec<E::Scalar>> = Vec::with_capacity(pairs);
let mut next_B: Vec<Vec<E::Scalar>> = Vec::with_capacity(pairs);
let mut next_C: Vec<Vec<E::Scalar>> = Vec::with_capacity(pairs);
next_A.par_extend((0..pairs).into_par_iter().map(|i| {
let lo = 2 * i;
let hi = lo + 1;
let mut v = vec![E::Scalar::ZERO; chunk_len];
v.iter_mut().enumerate().for_each(|(k, val)| {
*val = A_layers[lo][k] + (A_layers[hi][k] - A_layers[lo][k]) * r_b;
});
v
}));
next_B.par_extend((0..pairs).into_par_iter().map(|i| {
let lo = 2 * i;
let hi = lo + 1;
let mut v = vec![E::Scalar::ZERO; chunk_len];
v.iter_mut().enumerate().for_each(|(k, val)| {
*val = B_layers[lo][k] + (B_layers[hi][k] - B_layers[lo][k]) * r_b;
});
v
}));
next_C.par_extend((0..pairs).into_par_iter().map(|i| {
let lo = 2 * i;
let hi = lo + 1;
let mut v = vec![E::Scalar::ZERO; chunk_len];
v.iter_mut().enumerate().for_each(|(k, val)| {
*val = C_layers[lo][k] + (C_layers[hi][k] - C_layers[lo][k]) * r_b;
});
v
}));
A_layers = next_A;
B_layers = next_B;
C_layers = next_C;
m = pairs;
}
let T_out = T_cur * acc_eq.invert().unwrap();
vc.t_out_step = T_out;
vc.eq_rho_at_rb = acc_eq;
let _ =
SatisfyingAssignment::<E>::process_round(vc_state, vc_shape, vc_ck, vc, ell_b, transcript)?;
let (_fold_final_span, fold_final_t) = start_span!("fold_instances_witnesses");
let folded_W = R1CSWitness::fold_multiple(&r_bs, &Ws)?;
let folded_U = R1CSInstance::fold_multiple(&r_bs, &Us);
info!(elapsed_ms = %fold_final_t.elapsed().as_millis(), "fold_instances_witnesses");
Ok((
E_eq,
A_layers[0].clone(),
B_layers[0].clone(),
C_layers[0].clone(),
folded_W,
folded_U,
))
}
}
#[derive(Serialize, Deserialize)]
#[serde(bound = "")]
pub struct NeutronNovaProverKey<E: Engine> {
ck: CommitmentKey<E>,
S_step: SplitR1CSShape<E>,
S_core: SplitR1CSShape<E>,
vk_digest: SpartanDigest, vc_shape: SplitMultiRoundR1CSShape<E>,
vc_shape_regular: R1CSShape<E>,
vc_ck: CommitmentKey<E>,
}
#[derive(Serialize, Deserialize)]
#[serde(bound = "")]
pub struct NeutronNovaVerifierKey<E: Engine> {
ck: CommitmentKey<E>,
vk_ee: <E::PCS as PCSEngineTrait<E>>::VerifierKey,
S_step: SplitR1CSShape<E>,
S_core: SplitR1CSShape<E>,
vc_shape: SplitMultiRoundR1CSShape<E>,
vc_shape_regular: R1CSShape<E>,
vc_ck: CommitmentKey<E>,
#[serde(skip, default = "OnceCell::new")]
digest: OnceCell<SpartanDigest>,
}
impl<E: Engine> SimpleDigestible for NeutronNovaVerifierKey<E> {}
impl<E: Engine> DigestHelperTrait<E> for NeutronNovaVerifierKey<E> {
fn digest(&self) -> Result<SpartanDigest, SpartanError> {
self
.digest
.get_or_try_init(|| {
let dc = DigestComputer::<_>::new(self);
dc.digest()
})
.cloned()
.map_err(|_| SpartanError::DigestError {
reason: "Unable to compute digest for SpartanVerifierKey".to_string(),
})
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(bound = "")]
pub struct NeutronNovaPrepZkSNARK<E: Engine> {
ps_step: Vec<PrecommittedState<E>>,
ps_core: PrecommittedState<E>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(bound = "")]
pub struct NeutronNovaZkSNARK<E: Engine> {
step_instances: Vec<SplitR1CSInstance<E>>,
core_instance: SplitR1CSInstance<E>,
eval_arg: <E::PCS as PCSEngineTrait<E>>::EvaluationArgument,
U_verifier: SplitMultiRoundR1CSInstance<E>,
nifs: NovaNIFS<E>,
random_U: RelaxedR1CSInstance<E>,
folded_W: RelaxedR1CSWitness<E>,
}
impl<E: Engine> NeutronNovaZkSNARK<E>
where
E::PCS: FoldingEngineTrait<E>,
{
pub fn setup<C1: SpartanCircuit<E>, C2: SpartanCircuit<E>>(
step_circuit: &C1,
core_circuit: &C2,
num_steps: usize,
) -> Result<(NeutronNovaProverKey<E>, NeutronNovaVerifierKey<E>), SpartanError> {
debug!("Synthesizing step circuit");
let mut S_step = ShapeCS::r1cs_shape(step_circuit)?;
debug!("Finished synthesizing step circuit");
debug!("Synthesizing core circuit");
let mut S_core = ShapeCS::r1cs_shape(core_circuit)?;
debug!("Finished synthesizing core circuit");
SplitR1CSShape::equalize(&mut S_step, &mut S_core);
info!(
"Step circuit's witness sizes: shared = {}, precommitted = {}, rest = {}",
S_step.num_shared, S_step.num_precommitted, S_step.num_rest
);
info!(
"Core circuit's witness sizes: shared = {}, precommitted = {}, rest = {}",
S_core.num_shared, S_core.num_precommitted, S_core.num_rest
);
let (ck, vk_ee) = SplitR1CSShape::commitment_key(&[&S_step, &S_core])?;
let num_rounds_b = num_steps.next_power_of_two().log_2();
let num_vars = S_step.num_shared + S_step.num_precommitted + S_step.num_rest;
let num_rounds_x = usize::try_from(S_step.num_cons.ilog2()).unwrap();
let num_rounds_y = usize::try_from(num_vars.ilog2()).unwrap() + 1;
let vc = NeutronNovaVerifierCircuit::<E>::default(num_rounds_b, num_rounds_x, num_rounds_y);
let (vc_shape, vc_ck, _vk_mr) =
<ShapeCS<E> as MultiRoundSpartanShape<E>>::multiround_r1cs_shape(&vc)?;
let vc_shape_regular = vc_shape.to_regular_shape();
let vk: NeutronNovaVerifierKey<E> = NeutronNovaVerifierKey {
ck: ck.clone(),
S_step: S_step.clone(),
S_core: S_core.clone(),
vk_ee,
vc_shape: vc_shape.clone(),
vc_shape_regular: vc_shape_regular.clone(),
vc_ck: vc_ck.clone(),
digest: OnceCell::new(),
};
let pk = NeutronNovaProverKey {
ck,
S_step,
S_core,
vc_shape,
vc_shape_regular,
vc_ck,
vk_digest: vk.digest()?,
};
Ok((pk, vk))
}
pub fn prep_prove<C1: SpartanCircuit<E>, C2: SpartanCircuit<E>>(
pk: &NeutronNovaProverKey<E>,
step_circuits: &[C1],
core_circuit: &C2,
is_small: bool, ) -> Result<NeutronNovaPrepZkSNARK<E>, SpartanError> {
let mut ps =
SatisfyingAssignment::shared_witness(&pk.S_step, &pk.ck, &step_circuits[0], is_small)?;
let ps_step = (0..step_circuits.len())
.into_par_iter()
.map(|i| {
let mut ps_i = ps.clone();
SatisfyingAssignment::precommitted_witness(
&mut ps_i,
&pk.S_step,
&pk.ck,
&step_circuits[i],
is_small,
)?;
Ok(ps_i)
})
.collect::<Result<Vec<_>, _>>()?;
SatisfyingAssignment::precommitted_witness(
&mut ps,
&pk.S_core,
&pk.ck,
core_circuit,
is_small,
)?;
Ok(NeutronNovaPrepZkSNARK {
ps_step,
ps_core: ps,
})
}
pub fn prove<C1: SpartanCircuit<E>, C2: SpartanCircuit<E>>(
pk: &NeutronNovaProverKey<E>,
step_circuits: &[C1],
core_circuit: &C2,
prep_snark: &NeutronNovaPrepZkSNARK<E>,
is_small: bool, ) -> Result<Self, SpartanError> {
let (_prove_span, prove_t) = start_span!("neutronnova_prove");
let mut ps_core = prep_snark.ps_core.rerandomize(&pk.ck, &pk.S_core)?;
let mut ps_step = prep_snark
.ps_step
.par_iter()
.map(|ps_i| {
ps_i.rerandomize_with_shared(
&pk.ck,
&pk.S_step,
&ps_core.comm_W_shared,
&ps_core.r_W_shared,
)
})
.collect::<Result<Vec<_>, _>>()?;
let (_gen_span, gen_t) = start_span!(
"generate_instances_witnesses",
step_circuits = step_circuits.len()
);
let (res_steps, res_core) = rayon::join(
|| {
ps_step
.par_iter_mut()
.zip(step_circuits.par_iter().enumerate())
.map(|(pre_state, (i, circuit))| {
let mut transcript = E::TE::new(b"neutronnova_prove");
transcript.absorb(b"vk", &pk.vk_digest);
transcript.absorb(
b"num_circuits",
&E::Scalar::from(step_circuits.len() as u64),
);
transcript.absorb(b"circuit_index", &E::Scalar::from(i as u64));
let public_values =
circuit
.public_values()
.map_err(|e| SpartanError::SynthesisError {
reason: format!("Circuit does not provide public IO: {e}"),
})?;
transcript.absorb(b"public_values", &public_values.as_slice());
SatisfyingAssignment::r1cs_instance_and_witness(
pre_state,
&pk.S_step,
&pk.ck,
circuit,
is_small,
&mut transcript,
)
})
.try_fold(
|| (Vec::new(), Vec::new()),
|mut acc, res: Result<_, SpartanError>| {
let (u, w) = res?;
acc.0.push(u);
acc.1.push(w);
Ok(acc)
},
)
.try_reduce(
|| (Vec::new(), Vec::new()),
|mut a, mut b| {
a.0.append(&mut b.0);
a.1.append(&mut b.1);
Ok(a)
},
)
},
|| {
let mut transcript = E::TE::new(b"neutronnova_prove");
transcript.absorb(b"vk", &pk.vk_digest);
let public_values_core =
core_circuit
.public_values()
.map_err(|e| SpartanError::SynthesisError {
reason: format!("Core circuit does not provide public IO: {e}"),
})?;
transcript.absorb(b"public_values", &public_values_core.as_slice());
SatisfyingAssignment::r1cs_instance_and_witness(
&mut ps_core,
&pk.S_core,
&pk.ck,
core_circuit,
is_small,
&mut transcript,
)
},
);
let ((step_instances, step_witnesses), (core_instance, core_witness)) = (res_steps?, res_core?);
info!(elapsed_ms = %gen_t.elapsed().as_millis(), step_circuits = step_circuits.len(), "generate_instances_witnesses");
let (_reg_span, reg_t) = start_span!("convert_to_regular_instances");
let step_instances_regular = step_instances
.iter()
.map(|u| u.to_regular_instance())
.collect::<Result<Vec<_>, _>>()?;
let core_instance_regular = core_instance.to_regular_instance()?;
info!(elapsed_ms = %reg_t.elapsed().as_millis(), "convert_to_regular_instances");
let mut transcript = E::TE::new(b"neutronnova_prove");
transcript.absorb(b"vk", &pk.vk_digest);
transcript.absorb(b"core_instance", &core_instance_regular);
let n_padded = step_instances_regular.len().next_power_of_two();
let num_vars = pk.S_step.num_shared + pk.S_step.num_precommitted + pk.S_step.num_rest;
let num_rounds_b = n_padded.log_2();
let num_rounds_x = pk.S_step.num_cons.log_2();
let num_rounds_y = num_vars.log_2() + 1;
let mut vc = NeutronNovaVerifierCircuit::<E>::default(num_rounds_b, num_rounds_x, num_rounds_y);
let mut vc_state = SatisfyingAssignment::<E>::initialize_multiround_witness(&pk.vc_shape)?;
let (_nifs_span, nifs_t) = start_span!("NIFS");
let (E_eq, Az_step, Bz_step, Cz_step, folded_W, folded_U) = NeutronNovaNIFS::<E>::prove(
&pk.S_step,
&step_instances_regular,
&step_witnesses,
&mut vc,
&mut vc_state,
&pk.vc_shape,
&pk.vc_ck,
&mut transcript,
)?;
info!(elapsed_ms = %nifs_t.elapsed().as_millis(), "NIFS");
let (_tensor_span, tensor_t) = start_span!("compute_tensor_and_poly_tau");
let (_ell, left, right) = compute_tensor_decomp(pk.S_step.num_cons);
let (E1, E2) = E_eq.split_at(left);
let mut full_E = vec![E::Scalar::ONE; left * right];
full_E
.par_chunks_mut(left)
.enumerate()
.for_each(|(i, row)| {
let e2 = E2[i];
row.iter_mut().zip(E1.iter()).for_each(|(val, e1)| {
*val = e2 * *e1;
});
});
let mut poly_tau = MultilinearPolynomial::new(full_E);
info!(elapsed_ms = %tensor_t.elapsed().as_millis(), "compute_tensor_and_poly_tau");
let (_mp_span, mp_t) = start_span!("prepare_multilinear_polys");
let (mut poly_Az_step, mut poly_Bz_step, mut poly_Cz_step) = (
MultilinearPolynomial::new(Az_step),
MultilinearPolynomial::new(Bz_step),
MultilinearPolynomial::new(Cz_step),
);
let (mut poly_Az_core, mut poly_Bz_core, mut poly_Cz_core) = {
let (_core_span, core_t) = start_span!("compute_core_polys");
let z = [
core_witness.W.clone(),
vec![E::Scalar::ONE],
core_instance.public_values.clone(),
core_instance.challenges.clone(),
]
.concat();
let (Az, Bz, Cz) = pk.S_core.multiply_vec(&z)?;
info!(elapsed_ms = %core_t.elapsed().as_millis(), "compute_core_polys");
(
MultilinearPolynomial::new(Az),
MultilinearPolynomial::new(Bz),
MultilinearPolynomial::new(Cz),
)
};
info!(elapsed_ms = %mp_t.elapsed().as_millis(), "prepare_multilinear_polys");
let outer_start_index = num_rounds_b + 1;
let (_sc_span, sc_t) = start_span!("outer_sumcheck (batched)");
let r_x = SumcheckProof::<E>::prove_cubic_with_additive_term_batched_zk(
num_rounds_x,
&mut poly_tau,
&mut poly_Az_step,
&mut poly_Az_core,
&mut poly_Bz_step,
&mut poly_Bz_core,
&mut poly_Cz_step,
&mut poly_Cz_core,
&mut vc,
&mut vc_state,
&pk.vc_shape,
&pk.vc_ck,
&mut transcript,
outer_start_index,
)?;
info!(elapsed_ms = %sc_t.elapsed().as_millis(), "outer_sumcheck (batched)");
vc.claim_Az_step = poly_Az_step[0];
vc.claim_Bz_step = poly_Bz_step[0];
vc.claim_Cz_step = poly_Cz_step[0];
vc.claim_Az_core = poly_Az_core[0];
vc.claim_Bz_core = poly_Bz_core[0];
vc.claim_Cz_core = poly_Cz_core[0];
vc.tau_at_rx = poly_tau[0];
let chals = SatisfyingAssignment::<E>::process_round(
&mut vc_state,
&pk.vc_shape,
&pk.vc_ck,
&vc,
outer_start_index + num_rounds_x,
&mut transcript,
)?;
let r = chals[0];
let claim_inner_joint_step = vc.claim_Az_step + r * vc.claim_Bz_step + r * r * vc.claim_Cz_step;
let claim_inner_joint_core = vc.claim_Az_core + r * vc.claim_Bz_core + r * r * vc.claim_Cz_core;
let (_eval_rx_span, eval_rx_t) = start_span!("compute_eval_rx");
let evals_rx = EqPolynomial::evals_from_points(&r_x);
info!(elapsed_ms = %eval_rx_t.elapsed().as_millis(), "compute_eval_rx");
let (_sparse_span, sparse_t) = start_span!("compute_eval_table_sparse");
let (evals_A_step, evals_B_step, evals_C_step) = pk.S_step.bind_row_vars(&evals_rx);
let (evals_A_core, evals_B_core, evals_C_core) = pk.S_core.bind_row_vars(&evals_rx);
info!(elapsed_ms = %sparse_t.elapsed().as_millis(), "compute_eval_table_sparse");
let (_abc_span, abc_t) = start_span!("prepare_poly_ABC");
let poly_ABC_step = (0..evals_A_step.len())
.into_par_iter()
.map(|i| evals_A_step[i] + r * evals_B_step[i] + r * r * evals_C_step[i])
.collect::<Vec<E::Scalar>>();
let poly_ABC_core = (0..evals_A_core.len())
.into_par_iter()
.map(|i| evals_A_core[i] + r * evals_B_core[i] + r * r * evals_C_core[i])
.collect::<Vec<E::Scalar>>();
info!(elapsed_ms = %abc_t.elapsed().as_millis(), "prepare_poly_ABC");
let (_sc2_span, sc2_t) = start_span!("inner_sumcheck (batched)");
debug!("Proving inner sum-check with {} rounds", num_rounds_y);
debug!(
"Inner sum-check sizes - poly_ABC_step: {}, poly_ABC_core: {}",
poly_ABC_step.len(),
poly_ABC_core.len()
);
let (r_y, evals) = SumcheckProof::<E>::prove_quad_batched_zk(
&[claim_inner_joint_step, claim_inner_joint_core],
num_rounds_y,
&mut MultilinearPolynomial::new(poly_ABC_step),
&mut MultilinearPolynomial::new(poly_ABC_core),
&mut MultilinearPolynomial::new({
let mut v = vec![E::Scalar::ZERO; num_vars * 2];
let w_len = folded_W.W.len();
v[..w_len].copy_from_slice(&folded_W.W);
v[w_len] = E::Scalar::ONE;
let x_len = folded_U.X.len();
v[w_len + 1..w_len + 1 + x_len].copy_from_slice(&folded_U.X);
v
}),
&mut MultilinearPolynomial::new({
let mut v = vec![E::Scalar::ZERO; num_vars * 2];
let w_len = core_witness.W.len();
v[..w_len].copy_from_slice(&core_witness.W);
v[w_len] = E::Scalar::ONE;
let x_len = core_instance_regular.X.len();
v[w_len + 1..w_len + 1 + x_len].copy_from_slice(&core_instance_regular.X);
v
}),
&mut vc,
&mut vc_state,
&pk.vc_shape,
&pk.vc_ck,
&mut transcript,
outer_start_index + num_rounds_x + 1,
)?;
info!(elapsed_ms = %sc2_t.elapsed().as_millis(), "inner_sumcheck (batched)");
let eval_Z_step = evals[2];
let eval_Z_core = evals[3];
let eval_X_step = {
let X = vec![E::Scalar::ONE]
.into_iter()
.chain(folded_U.X.iter().cloned())
.collect::<Vec<E::Scalar>>();
let num_vars_log2 = usize::try_from(num_vars.ilog2()).unwrap();
SparsePolynomial::new(num_vars_log2, X).evaluate(&r_y[1..])
};
let eval_X_core = {
let X = vec![E::Scalar::ONE]
.into_iter()
.chain(core_instance_regular.X.iter().cloned())
.collect::<Vec<E::Scalar>>();
let num_vars_log2 = usize::try_from(num_vars.ilog2()).unwrap();
SparsePolynomial::new(num_vars_log2, X).evaluate(&r_y[1..])
};
let eval_W_step =
(eval_Z_step - r_y[0] * eval_X_step) * (E::Scalar::ONE - r_y[0]).invert().unwrap();
let eval_W_core =
(eval_Z_core - r_y[0] * eval_X_core) * (E::Scalar::ONE - r_y[0]).invert().unwrap();
vc.eval_W_step = eval_W_step;
vc.eval_W_core = eval_W_core;
vc.eval_X_step = eval_X_step;
vc.eval_X_core = eval_X_core;
let _ = SatisfyingAssignment::<E>::process_round(
&mut vc_state,
&pk.vc_shape,
&pk.vc_ck,
&vc,
outer_start_index + num_rounds_x + 1 + num_rounds_y,
&mut transcript,
)?;
let eval_w_step_commit_round = outer_start_index + num_rounds_x + 1 + num_rounds_y + 1;
let _ = SatisfyingAssignment::<E>::process_round(
&mut vc_state,
&pk.vc_shape,
&pk.vc_ck,
&vc,
eval_w_step_commit_round,
&mut transcript,
)?;
let _ = SatisfyingAssignment::<E>::process_round(
&mut vc_state,
&pk.vc_shape,
&pk.vc_ck,
&vc,
eval_w_step_commit_round + 1,
&mut transcript,
)?;
let (U_verifier, W_verifier) =
SatisfyingAssignment::<E>::finalize_multiround_witness(&mut vc_state, &pk.vc_shape)?;
let U_verifier_regular = U_verifier.to_regular_instance()?;
let (random_U, random_W) = pk
.vc_shape_regular
.sample_random_instance_witness(&pk.vc_ck)?;
let (nifs, folded_W_verifier) = NovaNIFS::<E>::prove(
&pk.vc_ck,
&pk.vc_shape_regular,
&random_U,
&random_W,
&U_verifier_regular,
&W_verifier,
&mut transcript,
)?;
let comm_eval_W_step = U_verifier.comm_w_per_round[eval_w_step_commit_round].clone();
let blind_eval_W_step = vc_state.r_w_per_round[eval_w_step_commit_round].clone();
let comm_eval_W_core = U_verifier.comm_w_per_round[eval_w_step_commit_round + 1].clone();
let blind_eval_W_core = vc_state.r_w_per_round[eval_w_step_commit_round + 1].clone();
let c_eval = transcript.squeeze(b"c_eval")?;
let (_fold_eval_span, fold_eval_t) = start_span!("fold_evaluation_claims");
let comm = <E::PCS as FoldingEngineTrait<E>>::fold_commitments(
&[folded_U.comm_W, core_instance_regular.comm_W],
&[E::Scalar::ONE, c_eval],
)?;
let blind = <E::PCS as FoldingEngineTrait<E>>::fold_blinds(
&[folded_W.r_W.clone(), core_witness.r_W.clone()],
&[E::Scalar::ONE, c_eval],
)?;
let W = folded_W
.W
.par_iter()
.zip(core_witness.W.par_iter())
.map(|(w1, w2)| *w1 + c_eval * *w2)
.collect::<Vec<_>>();
let comm_eval = <E::PCS as FoldingEngineTrait<E>>::fold_commitments(
&[comm_eval_W_step, comm_eval_W_core],
&[E::Scalar::ONE, c_eval],
)?;
let blind_eval = <E::PCS as FoldingEngineTrait<E>>::fold_blinds(
&[blind_eval_W_step, blind_eval_W_core],
&[E::Scalar::ONE, c_eval],
)?;
info!(elapsed_ms = %fold_eval_t.elapsed().as_millis(), "fold_evaluation_claims");
let (_pcs_span, pcs_t) = start_span!("pcs_prove");
let eval_arg = E::PCS::prove(
&pk.ck,
&pk.vc_ck,
&mut transcript,
&comm,
&W,
&blind,
&r_y[1..],
&comm_eval,
&blind_eval,
)?;
info!(elapsed_ms = %pcs_t.elapsed().as_millis(), "pcs_prove");
let result = Self {
step_instances,
core_instance,
eval_arg,
U_verifier,
nifs,
random_U,
folded_W: folded_W_verifier,
};
info!(elapsed_ms = %prove_t.elapsed().as_millis(), "neutronnova_prove");
Ok(result)
}
pub fn verify(
&self,
vk: &NeutronNovaVerifierKey<E>,
num_instances: usize,
) -> Result<(Vec<Vec<E::Scalar>>, Vec<E::Scalar>), SpartanError> {
let (_verify_span, _verify_t) = start_span!("neutronnova_verify");
if num_instances != self.step_instances.len() {
return Err(SpartanError::ProofVerifyError {
reason: format!(
"Expected {} instances, got {}",
num_instances,
self.step_instances.len()
),
});
}
let (_validate_span, validate_t) =
start_span!("validate_instances", instances = self.step_instances.len());
for (i, u) in self.step_instances.iter().enumerate() {
let mut transcript = E::TE::new(b"neutronnova_prove");
transcript.absorb(b"vk", &vk.digest()?);
transcript.absorb(
b"num_circuits",
&E::Scalar::from(self.step_instances.len() as u64),
);
transcript.absorb(b"circuit_index", &E::Scalar::from(i as u64));
transcript.absorb(b"public_values", &u.public_values.as_slice());
u.validate(&vk.S_step, &mut transcript)?;
}
let mut transcript = E::TE::new(b"neutronnova_prove");
transcript.absorb(b"vk", &vk.digest()?);
transcript.absorb(
b"public_values",
&self.core_instance.public_values.as_slice(),
);
self.core_instance.validate(&vk.S_core, &mut transcript)?;
info!(elapsed_ms = %validate_t.elapsed().as_millis(), instances = self.step_instances.len(), "validate_instances");
for u in &self.step_instances {
if u.comm_W_shared != self.core_instance.comm_W_shared {
return Err(SpartanError::ProofVerifyError {
reason: "All instances must have the same shared commitment".to_string(),
});
}
}
let (_convert_span, convert_t) = start_span!("convert_to_regular_verify");
let mut step_instances_padded = self.step_instances.clone();
if step_instances_padded.len() != step_instances_padded.len().next_power_of_two() {
step_instances_padded.extend(std::iter::repeat_n(
step_instances_padded[0].clone(),
step_instances_padded.len().next_power_of_two() - step_instances_padded.len(),
));
}
let step_instances_regular = step_instances_padded
.par_iter()
.map(|u| u.to_regular_instance())
.collect::<Result<Vec<_>, _>>()?;
let core_instance_regular = self.core_instance.to_regular_instance()?;
info!(elapsed_ms = %convert_t.elapsed().as_millis(), "convert_to_regular_verify");
let mut transcript = E::TE::new(b"neutronnova_prove");
transcript.absorb(b"vk", &vk.digest()?);
transcript.absorb(b"core_instance", &core_instance_regular);
for U in step_instances_regular.iter() {
transcript.absorb(b"U", U);
}
transcript.absorb(b"T", &E::Scalar::ZERO);
let num_rounds_b = step_instances_regular.len().log_2();
let num_vars = vk.S_step.num_shared + vk.S_step.num_precommitted + vk.S_step.num_rest;
let num_rounds_x = vk.S_step.num_cons.log_2();
let num_rounds_y = num_vars.log_2() + 1;
let tau = transcript.squeeze(b"tau")?;
let rhos = (0..num_rounds_b)
.map(|_| transcript.squeeze(b"rho"))
.collect::<Result<Vec<_>, _>>()?;
self.U_verifier.validate(&vk.vc_shape, &mut transcript)?;
let U_verifier_regular = self.U_verifier.to_regular_instance()?;
let num_public_values = 6usize;
let num_challenges = num_rounds_b + num_rounds_x + 1 + num_rounds_y;
if U_verifier_regular.X.len() != num_challenges + num_public_values {
return Err(SpartanError::ProofVerifyError {
reason: format!(
"Verifier instance has incorrect number of public IO: expected {}, got {}",
num_challenges + num_public_values,
U_verifier_regular.X.len()
),
});
}
let challenges = &U_verifier_regular.X[0..num_challenges];
let public_values = &U_verifier_regular.X[num_challenges..num_challenges + 6];
let r_b = challenges[0..num_rounds_b].to_vec();
let r_x = challenges[num_rounds_b..num_rounds_b + num_rounds_x].to_vec();
let r = challenges[num_rounds_b + num_rounds_x]; let r_y = challenges[num_rounds_b + num_rounds_x + 1..].to_vec();
let folded_U = R1CSInstance::fold_multiple(&r_b, &step_instances_regular);
let folded_U_verifier =
self
.nifs
.verify(&mut transcript, &self.random_U, &U_verifier_regular)?;
vk.vc_shape_regular
.is_sat_relaxed(&vk.vc_ck, &folded_U_verifier, &self.folded_W)
.map_err(|e| SpartanError::ProofVerifyError {
reason: format!("Folded instance not satisfiable: {e}"),
})?;
let (_matrix_eval_span, matrix_eval_t) = start_span!("matrix_evaluations");
let (eval_A_step, eval_B_step, eval_C_step, eval_A_core, eval_B_core, eval_C_core) = {
let T_x = EqPolynomial::evals_from_points(&r_x);
let T_y = EqPolynomial::evals_from_points(&r_y);
let (eval_A_step, eval_B_step, eval_C_step) = vk.S_step.evaluate_with_tables(&T_x, &T_y);
let (eval_A_core, eval_B_core, eval_C_core) = vk.S_core.evaluate_with_tables(&T_x, &T_y);
(
eval_A_step,
eval_B_step,
eval_C_step,
eval_A_core,
eval_B_core,
eval_C_core,
)
};
info!(elapsed_ms = %matrix_eval_t.elapsed().as_millis(), "matrix_evaluations");
let eval_X_step = {
let X = vec![E::Scalar::ONE]
.into_iter()
.chain(folded_U.X.iter().cloned())
.collect::<Vec<E::Scalar>>();
let num_vars_log2 = usize::try_from(num_vars.ilog2()).unwrap();
SparsePolynomial::new(num_vars_log2, X).evaluate(&r_y[1..])
};
let eval_X_core = {
let X = vec![E::Scalar::ONE]
.into_iter()
.chain(core_instance_regular.X.iter().cloned())
.collect::<Vec<E::Scalar>>();
let num_vars_log2 = usize::try_from(num_vars.ilog2()).unwrap();
SparsePolynomial::new(num_vars_log2, X).evaluate(&r_y[1..])
};
let quotient_step = eval_A_step + r * eval_B_step + r * r * eval_C_step;
let quotient_core = eval_A_core + r * eval_B_core + r * r * eval_C_core;
let tau_at_rx = PowPolynomial::new(&tau, r_x.len()).evaluate(&r_x)?;
let eq_rho_at_rb = EqPolynomial::new(r_b).evaluate(&rhos);
if public_values[0] != tau_at_rx
|| public_values[1] != eval_X_step
|| public_values[2] != eval_X_core
|| public_values[3] != eq_rho_at_rb
|| public_values[4] != quotient_step
|| public_values[5] != quotient_core
{
return Err(SpartanError::ProofVerifyError {
reason:
"Verifier instance public tau_at_rx/eval_X_step/eq_rho_at_rb/eval_X_core/quotients do not match recomputation"
.to_string(),
});
}
let c_eval = transcript.squeeze(b"c_eval")?;
let eval_w_step_commit_round = num_rounds_b + 1 + num_rounds_x + 1 + num_rounds_y + 1;
let comm_eval_W_step = self.U_verifier.comm_w_per_round[eval_w_step_commit_round].clone();
let comm_eval_W_core = self.U_verifier.comm_w_per_round[eval_w_step_commit_round + 1].clone();
let comm = <E::PCS as FoldingEngineTrait<E>>::fold_commitments(
&[folded_U.comm_W, core_instance_regular.comm_W],
&[E::Scalar::ONE, c_eval],
)?;
let comm_eval = <E::PCS as FoldingEngineTrait<E>>::fold_commitments(
&[comm_eval_W_step, comm_eval_W_core],
&[E::Scalar::ONE, c_eval],
)?;
let (_pcs_verify_span, pcs_verify_t) = start_span!("pcs_verify");
E::PCS::verify(
&vk.vk_ee,
&vk.vc_ck,
&mut transcript,
&comm,
&r_y[1..],
&comm_eval,
&self.eval_arg,
)?;
info!(elapsed_ms = %pcs_verify_t.elapsed().as_millis(), "pcs_verify");
info!(elapsed_ms = %_verify_t.elapsed().as_millis(), "neutronnova_verify");
let public_values_step = self
.step_instances
.iter()
.take(num_instances)
.map(|u| u.public_values.clone())
.collect::<Vec<Vec<_>>>();
let public_values_core = self.core_instance.public_values.clone();
Ok((public_values_step, public_values_core))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::provider::T256HyraxEngine;
use bellpepper::gadgets::{
boolean::{AllocatedBit, Boolean},
num::AllocatedNum,
sha256::sha256,
};
use bellpepper_core::{ConstraintSystem, SynthesisError};
use core::marker::PhantomData;
#[derive(Clone, Debug)]
struct Sha256Circuit<E: Engine> {
preimage: Vec<u8>,
_p: PhantomData<E>,
}
impl<E: Engine> SpartanCircuit<E> for Sha256Circuit<E> {
fn public_values(&self) -> Result<Vec<E::Scalar>, SynthesisError> {
Ok(vec![E::Scalar::ZERO]) }
fn shared<CS: ConstraintSystem<E::Scalar>>(
&self,
_: &mut CS,
) -> Result<Vec<AllocatedNum<E::Scalar>>, SynthesisError> {
Ok(vec![]) }
fn precommitted<CS: ConstraintSystem<E::Scalar>>(
&self,
_: &mut CS,
_: &[AllocatedNum<E::Scalar>],
) -> Result<Vec<AllocatedNum<E::Scalar>>, SynthesisError> {
Ok(vec![]) }
fn num_challenges(&self) -> usize {
0 }
fn synthesize<CS: ConstraintSystem<E::Scalar>>(
&self,
cs: &mut CS,
_shared: &[AllocatedNum<E::Scalar>],
_precommitted: &[AllocatedNum<E::Scalar>],
_challenges: Option<&[E::Scalar]>, ) -> 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 generate_sha_r1cs<E: Engine>(
num_circuits: usize,
len: usize,
) -> (
NeutronNovaProverKey<E>,
NeutronNovaVerifierKey<E>,
Vec<Sha256Circuit<E>>,
)
where
E::PCS: FoldingEngineTrait<E>, {
let circuit = Sha256Circuit::<E> {
preimage: vec![0u8; len],
_p: Default::default(),
};
let (pk, vk) = NeutronNovaZkSNARK::<E>::setup(&circuit, &circuit, num_circuits).unwrap();
let circuits = (0..num_circuits)
.map(|i| Sha256Circuit::<E> {
preimage: vec![i as u8; len],
_p: Default::default(),
})
.collect::<Vec<_>>();
(pk, vk, circuits)
}
fn test_neutron_inner<E: Engine, C1: SpartanCircuit<E>, C2: SpartanCircuit<E>>(
name: &str,
pk: &NeutronNovaProverKey<E>,
vk: &NeutronNovaVerifierKey<E>,
step_circuits: &[C1],
core_circuit: &C2,
) where
E::PCS: FoldingEngineTrait<E>,
{
println!(
"[bench_neutron_inner] name: {name}, num_circuits: {}",
step_circuits.len()
);
let ps = NeutronNovaZkSNARK::<E>::prep_prove(pk, step_circuits, core_circuit, true).unwrap();
let res = NeutronNovaZkSNARK::prove(pk, step_circuits, core_circuit, &ps, true);
assert!(res.is_ok());
let snark = res.unwrap();
let res = snark.verify(vk, step_circuits.len());
println!(
"[bench_neutron_inner] name: {name}, num_circuits: {}, verify res: {:?}",
step_circuits.len(),
res
);
assert!(res.is_ok());
let (public_values_step, _public_values_core) = res.unwrap();
assert_eq!(public_values_step.len(), step_circuits.len());
}
#[test]
fn test_neutron_sha256() {
type E = T256HyraxEngine;
for num_circuits in [2, 7, 32, 64] {
for len in [32, 64].iter() {
let (pk, vk, circuits) = generate_sha_r1cs::<E>(num_circuits, *len);
test_neutron_inner(
&format!("sha256_num_circuits={num_circuits}_len={len}"),
&pk,
&vk,
&circuits,
&circuits[0], );
}
}
}
}