Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Streamline Nova allocations (Arecibo backport) #277

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions src/bellpepper/r1cs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use ff::PrimeField;
pub trait NovaWitness<E: Engine> {
/// Return an instance and witness, given a shape and ck.
fn r1cs_instance_and_witness(
&self,
self,
shape: &R1CSShape<E>,
ck: &CommitmentKey<E>,
) -> Result<(R1CSInstance<E>, R1CSWitness<E>), NovaError>;
Expand All @@ -32,16 +32,17 @@ pub trait NovaShape<E: Engine> {

impl<E: Engine> NovaWitness<E> for SatisfyingAssignment<E> {
fn r1cs_instance_and_witness(
&self,
self,
shape: &R1CSShape<E>,
ck: &CommitmentKey<E>,
) -> Result<(R1CSInstance<E>, R1CSWitness<E>), NovaError> {
let W = R1CSWitness::<E>::new(shape, self.aux_assignment())?;
let X = &self.input_assignment()[1..];
let (input_assignment, aux_assignment) = self.to_assignments();
let W = R1CSWitness::<E>::new(shape, aux_assignment)?;
let X = input_assignment[1..].to_owned();

let comm_W = W.commit(ck);

let instance = R1CSInstance::<E>::new(shape, &comm_W, X)?;
let instance = R1CSInstance::<E>::new(shape, comm_W, X)?;

Ok((instance, W))
}
Expand Down
110 changes: 83 additions & 27 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,15 @@ pub mod traits;

use once_cell::sync::OnceCell;

use crate::bellpepper::{
r1cs::{NovaShape, NovaWitness},
shape_cs::ShapeCS,
solver::SatisfyingAssignment,
};
use crate::digest::{DigestComputer, SimpleDigestible};
use crate::{
bellpepper::{
r1cs::{NovaShape, NovaWitness},
shape_cs::ShapeCS,
solver::SatisfyingAssignment,
},
r1cs::R1CSResult,
};
use bellpepper_core::{ConstraintSystem, SynthesisError};
use circuit::{NovaAugmentedCircuit, NovaAugmentedCircuitInputs, NovaAugmentedCircuitParams};
use constants::{BN_LIMB_WIDTH, BN_N_LIMBS, NUM_FE_WITHOUT_IO_FOR_CRHF, NUM_HASH_BITS};
Expand Down Expand Up @@ -229,6 +232,21 @@ where
}
}

/// A resource buffer for [`RecursiveSNARK`] for storing scratch values that are computed by `prove_step`,
/// which allows the reuse of memory allocations and avoids unnecessary new allocations in the critical section.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(bound = "")]
pub struct ResourceBuffer<E: Engine> {
l_w: Option<R1CSWitness<E>>,
l_u: Option<R1CSInstance<E>>,

ABC_Z_1: R1CSResult<E>,
ABC_Z_2: R1CSResult<E>,

/// buffer for `commit_T`
T: Vec<E::Scalar>,
}

/// A SNARK that proves the correct execution of an incremental computation
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(bound = "")]
Expand All @@ -247,6 +265,12 @@ where
r_U_secondary: RelaxedR1CSInstance<E2>,
l_w_secondary: R1CSWitness<E2>,
l_u_secondary: R1CSInstance<E2>,

/// Buffer for memory needed by the primary fold-step
buffer_primary: ResourceBuffer<E1>,
/// Buffer for memory needed by the secondary fold-step
buffer_secondary: ResourceBuffer<E2>,

i: usize,
zi_primary: Vec<E1::Scalar>,
zi_secondary: Vec<E2::Scalar>,
Expand All @@ -272,6 +296,9 @@ where
return Err(NovaError::InvalidInitialInputLength);
}

let r1cs_primary = &pp.r1cs_shape_primary;
let r1cs_secondary = &pp.r1cs_shape_secondary;

// base case for the primary
let mut cs_primary = SatisfyingAssignment::<E1>::new();
let inputs_primary: NovaAugmentedCircuitInputs<E2> = NovaAugmentedCircuitInputs::new(
Expand Down Expand Up @@ -318,16 +345,15 @@ where
// IVC proof for the primary circuit
let l_w_primary = w_primary;
let l_u_primary = u_primary;
let r_W_primary = RelaxedR1CSWitness::from_r1cs_witness(&pp.r1cs_shape_primary, &l_w_primary);
let r_W_primary = RelaxedR1CSWitness::from_r1cs_witness(&pp.r1cs_shape_primary, l_w_primary);
let r_U_primary =
RelaxedR1CSInstance::from_r1cs_instance(&pp.ck_primary, &pp.r1cs_shape_primary, &l_u_primary);
RelaxedR1CSInstance::from_r1cs_instance(&pp.ck_primary, &pp.r1cs_shape_primary, l_u_primary);

// IVC proof for the secondary circuit
let l_w_secondary = w_secondary;
let l_u_secondary = u_secondary;
let r_W_secondary = RelaxedR1CSWitness::<E2>::default(&pp.r1cs_shape_secondary);
let r_U_secondary =
RelaxedR1CSInstance::<E2>::default(&pp.ck_secondary, &pp.r1cs_shape_secondary);
let r_W_secondary = RelaxedR1CSWitness::<E2>::default(r1cs_secondary);
let r_U_secondary = RelaxedR1CSInstance::<E2>::default(&pp.ck_secondary, r1cs_secondary);

assert!(
!(zi_primary.len() != pp.F_arity_primary || zi_secondary.len() != pp.F_arity_secondary),
Expand All @@ -344,6 +370,22 @@ where
.map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing))
.collect::<Result<Vec<<E2 as Engine>::Scalar>, _>>()?;

let buffer_primary = ResourceBuffer {
l_w: None,
l_u: None,
ABC_Z_1: R1CSResult::default(r1cs_primary),
ABC_Z_2: R1CSResult::default(r1cs_primary),
T: r1cs::default_T(r1cs_primary),
};

let buffer_secondary = ResourceBuffer {
l_w: None,
l_u: None,
ABC_Z_1: R1CSResult::default(r1cs_secondary),
ABC_Z_2: R1CSResult::default(r1cs_secondary),
T: r1cs::default_T(r1cs_secondary),
};

Ok(Self {
z0_primary: z0_primary.to_vec(),
z0_secondary: z0_secondary.to_vec(),
Expand All @@ -353,6 +395,9 @@ where
r_U_secondary,
l_w_secondary,
l_u_secondary,

buffer_primary,
buffer_secondary,
i: 0,
zi_primary,
zi_secondary,
Expand All @@ -374,26 +419,37 @@ where
return Ok(());
}

// save the inputs before proceeding to the `i+1`th step
let r_U_primary_i = self.r_U_primary.clone();
let r_U_secondary_i = self.r_U_secondary.clone();
let l_u_secondary_i = self.l_u_secondary.clone();

// fold the secondary circuit's instance
let (nifs_secondary, (r_U_secondary, r_W_secondary)) = NIFS::prove(
let nifs_secondary = NIFS::prove_mut(
&pp.ck_secondary,
&pp.ro_consts_secondary,
&scalar_as_base::<E1>(pp.digest()),
&pp.r1cs_shape_secondary,
&self.r_U_secondary,
&self.r_W_secondary,
&mut self.r_U_secondary,
&mut self.r_W_secondary,
&self.l_u_secondary,
&self.l_w_secondary,
&mut self.buffer_secondary.T,
&mut self.buffer_secondary.ABC_Z_1,
&mut self.buffer_secondary.ABC_Z_2,
)?;

let mut cs_primary = SatisfyingAssignment::<E1>::new();
let mut cs_primary = SatisfyingAssignment::<E1>::with_capacity(
pp.r1cs_shape_primary.num_io + 1,
pp.r1cs_shape_primary.num_vars,
);
let inputs_primary: NovaAugmentedCircuitInputs<E2> = NovaAugmentedCircuitInputs::new(
scalar_as_base::<E1>(pp.digest()),
E1::Scalar::from(self.i as u64),
self.z0_primary.to_vec(),
Some(self.zi_primary.clone()),
Some(self.r_U_secondary.clone()),
Some(self.l_u_secondary.clone()),
Some(r_U_secondary_i),
Some(l_u_secondary_i),
Some(Commitment::<E2>::decompress(&nifs_secondary.comm_T)?),
);

Expand All @@ -409,24 +465,30 @@ where
cs_primary.r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.ck_primary)?;

// fold the primary circuit's instance
let (nifs_primary, (r_U_primary, r_W_primary)) = NIFS::prove(
let nifs_primary = NIFS::prove_mut(
&pp.ck_primary,
&pp.ro_consts_primary,
&pp.digest(),
&pp.r1cs_shape_primary,
&self.r_U_primary,
&self.r_W_primary,
&mut self.r_U_primary,
&mut self.r_W_primary,
&l_u_primary,
&l_w_primary,
&mut self.buffer_primary.T,
&mut self.buffer_primary.ABC_Z_1,
&mut self.buffer_primary.ABC_Z_2,
)?;

let mut cs_secondary = SatisfyingAssignment::<E2>::new();
let mut cs_secondary = SatisfyingAssignment::<E2>::with_capacity(
pp.r1cs_shape_secondary.num_io + 1,
pp.r1cs_shape_secondary.num_vars,
);
let inputs_secondary: NovaAugmentedCircuitInputs<E1> = NovaAugmentedCircuitInputs::new(
pp.digest(),
E2::Scalar::from(self.i as u64),
self.z0_secondary.to_vec(),
Some(self.zi_secondary.clone()),
Some(self.r_U_primary.clone()),
Some(r_U_primary_i),
Some(l_u_primary),
Some(Commitment::<E1>::decompress(&nifs_primary.comm_T)?),
);
Expand Down Expand Up @@ -456,14 +518,8 @@ where
self.l_u_secondary = l_u_secondary;
self.l_w_secondary = l_w_secondary;

self.r_U_primary = r_U_primary;
self.r_W_primary = r_W_primary;

self.i += 1;

self.r_U_secondary = r_U_secondary;
self.r_W_secondary = r_W_secondary;

Ok(())
}

Expand Down
57 changes: 54 additions & 3 deletions src/nifs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
use crate::{
constants::{NUM_CHALLENGE_BITS, NUM_FE_FOR_RO},
errors::NovaError,
r1cs::{R1CSInstance, R1CSShape, R1CSWitness, RelaxedR1CSInstance, RelaxedR1CSWitness},
r1cs::{
R1CSInstance, R1CSResult, R1CSShape, R1CSWitness, RelaxedR1CSInstance, RelaxedR1CSWitness,
},
scalar_as_base,
traits::{commitment::CommitmentTrait, AbsorbInROTrait, Engine, ROTrait},
Commitment, CommitmentKey, CompressedCommitment,
Expand Down Expand Up @@ -78,6 +80,55 @@ impl<E: Engine> NIFS<E> {
))
}

/// Takes as input a Relaxed R1CS instance-witness tuple `(U1, W1)` and
/// an R1CS instance-witness tuple `(U2, W2)` with the same structure `shape`
/// and defined with respect to the same `ck`, and updates `(U1, W1)` by folding
/// `(U2, W2)` into it with the guarantee that the updated witness `W` satisfies
/// the updated instance `U` if and only if `W1` satisfies `U1` and `W2` satisfies `U2`.
#[allow(clippy::too_many_arguments)]
pub fn prove_mut(
ck: &CommitmentKey<E>,
ro_consts: &ROConstants<E>,
pp_digest: &E::Scalar,
S: &R1CSShape<E>,
U1: &mut RelaxedR1CSInstance<E>,
W1: &mut RelaxedR1CSWitness<E>,
U2: &R1CSInstance<E>,
W2: &R1CSWitness<E>,
T: &mut Vec<E::Scalar>,
ABC_Z_1: &mut R1CSResult<E>,
ABC_Z_2: &mut R1CSResult<E>,
) -> Result<NIFS<E>, NovaError> {
// initialize a new RO
let mut ro = E::RO::new(ro_consts.clone(), NUM_FE_FOR_RO);

// append the digest of pp to the transcript
ro.absorb(scalar_as_base::<E>(*pp_digest));

// append U2 to transcript, U1 does not need to absorbed since U2.X[0] = Hash(params, U1, i, z0, zi)
U2.absorb_in_ro(&mut ro);

// compute a commitment to the cross-term
let comm_T = S.commit_T_into(ck, U1, W1, U2, W2, T, ABC_Z_1, ABC_Z_2)?;

// append `comm_T` to the transcript and obtain a challenge
comm_T.absorb_in_ro(&mut ro);

// compute a challenge from the RO
let r = ro.squeeze(NUM_CHALLENGE_BITS);

// fold the instance using `r` and `comm_T`
U1.fold_mut(U2, &comm_T, &r);

// fold the witness using `r` and `T`
W1.fold_mut(W2, T, &r)?;

// return the commitment
Ok(Self {
comm_T: comm_T.compress(),
})
}

/// Takes as input a relaxed R1CS instance `U1` and R1CS instance `U2`
/// with the same shape and defined with respect to the same parameters,
/// and outputs a folded instance `U` with the same shape,
Expand Down Expand Up @@ -349,13 +400,13 @@ mod tests {
};

let W = {
let res = R1CSWitness::new(&S, &vars);
let res = R1CSWitness::new(&S, vars);
assert!(res.is_ok());
res.unwrap()
};
let U = {
let comm_W = W.commit(ck);
let res = R1CSInstance::new(&S, &comm_W, &X);
let res = R1CSInstance::new(&S, comm_W, X);
assert!(res.is_ok());
res.unwrap()
};
Expand Down
Loading