diff --git a/src/bellpepper/r1cs.rs b/src/bellpepper/r1cs.rs index 3a7fd535..c87b8227 100644 --- a/src/bellpepper/r1cs.rs +++ b/src/bellpepper/r1cs.rs @@ -16,7 +16,7 @@ use ff::PrimeField; pub trait NovaWitness { /// Return an instance and witness, given a shape and ck. fn r1cs_instance_and_witness( - &self, + self, shape: &R1CSShape, ck: &CommitmentKey, ) -> Result<(R1CSInstance, R1CSWitness), NovaError>; @@ -32,16 +32,17 @@ pub trait NovaShape { impl NovaWitness for SatisfyingAssignment { fn r1cs_instance_and_witness( - &self, + self, shape: &R1CSShape, ck: &CommitmentKey, ) -> Result<(R1CSInstance, R1CSWitness), NovaError> { - let W = R1CSWitness::::new(shape, self.aux_assignment())?; - let X = &self.input_assignment()[1..]; + let (input_assignment, aux_assignment) = self.to_assignments(); + let W = R1CSWitness::::new(shape, aux_assignment)?; + let X = input_assignment[1..].to_owned(); let comm_W = W.commit(ck); - let instance = R1CSInstance::::new(shape, &comm_W, X)?; + let instance = R1CSInstance::::new(shape, comm_W, X)?; Ok((instance, W)) } diff --git a/src/lib.rs b/src/lib.rs index a999f127..69d2b924 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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}; @@ -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 { + l_w: Option>, + l_u: Option>, + + ABC_Z_1: R1CSResult, + ABC_Z_2: R1CSResult, + + /// buffer for `commit_T` + T: Vec, +} + /// A SNARK that proves the correct execution of an incremental computation #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(bound = "")] @@ -247,6 +265,12 @@ where r_U_secondary: RelaxedR1CSInstance, l_w_secondary: R1CSWitness, l_u_secondary: R1CSInstance, + + /// Buffer for memory needed by the primary fold-step + buffer_primary: ResourceBuffer, + /// Buffer for memory needed by the secondary fold-step + buffer_secondary: ResourceBuffer, + i: usize, zi_primary: Vec, zi_secondary: Vec, @@ -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::::new(); let inputs_primary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( @@ -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::::default(&pp.r1cs_shape_secondary); - let r_U_secondary = - RelaxedR1CSInstance::::default(&pp.ck_secondary, &pp.r1cs_shape_secondary); + let r_W_secondary = RelaxedR1CSWitness::::default(r1cs_secondary); + let r_U_secondary = RelaxedR1CSInstance::::default(&pp.ck_secondary, r1cs_secondary); assert!( !(zi_primary.len() != pp.F_arity_primary || zi_secondary.len() != pp.F_arity_secondary), @@ -344,6 +370,22 @@ where .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) .collect::::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(), @@ -353,6 +395,9 @@ where r_U_secondary, l_w_secondary, l_u_secondary, + + buffer_primary, + buffer_secondary, i: 0, zi_primary, zi_secondary, @@ -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::(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::::new(); + let mut cs_primary = SatisfyingAssignment::::with_capacity( + pp.r1cs_shape_primary.num_io + 1, + pp.r1cs_shape_primary.num_vars, + ); let inputs_primary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( scalar_as_base::(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::::decompress(&nifs_secondary.comm_T)?), ); @@ -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::::new(); + let mut cs_secondary = SatisfyingAssignment::::with_capacity( + pp.r1cs_shape_secondary.num_io + 1, + pp.r1cs_shape_secondary.num_vars, + ); let inputs_secondary: NovaAugmentedCircuitInputs = 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::::decompress(&nifs_primary.comm_T)?), ); @@ -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(()) } diff --git a/src/nifs.rs b/src/nifs.rs index 0329d38b..10958ca8 100644 --- a/src/nifs.rs +++ b/src/nifs.rs @@ -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, @@ -78,6 +80,55 @@ impl NIFS { )) } + /// 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, + ro_consts: &ROConstants, + pp_digest: &E::Scalar, + S: &R1CSShape, + U1: &mut RelaxedR1CSInstance, + W1: &mut RelaxedR1CSWitness, + U2: &R1CSInstance, + W2: &R1CSWitness, + T: &mut Vec, + ABC_Z_1: &mut R1CSResult, + ABC_Z_2: &mut R1CSResult, + ) -> Result, 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::(*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, @@ -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() }; diff --git a/src/r1cs/mod.rs b/src/r1cs/mod.rs index 56ef1394..55037f4c 100644 --- a/src/r1cs/mod.rs +++ b/src/r1cs/mod.rs @@ -44,6 +44,14 @@ pub struct R1CSShape { impl SimpleDigestible for R1CSShape {} +/// A type that holds the result of a R1CS multiplication +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct R1CSResult { + pub(crate) AZ: Vec, + pub(crate) BZ: Vec, + pub(crate) CZ: Vec, +} + /// A type that holds a witness for a given R1CS instance #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct R1CSWitness { @@ -173,6 +181,55 @@ impl R1CSShape { Ok((Az, Bz, Cz)) } + pub(crate) fn multiply_witness( + &self, + W: &[E::Scalar], + u: &E::Scalar, + X: &[E::Scalar], + ) -> Result<(Vec, Vec, Vec), NovaError> { + if X.len() != self.num_io || W.len() != self.num_vars { + return Err(NovaError::InvalidWitnessLength); + } + + let (Az, (Bz, Cz)) = rayon::join( + || self.A.multiply_witness(W, u, X), + || { + rayon::join( + || self.B.multiply_witness(W, u, X), + || self.C.multiply_witness(W, u, X), + ) + }, + ); + + Ok((Az, Bz, Cz)) + } + + pub(crate) fn multiply_witness_into( + &self, + W: &[E::Scalar], + u: &E::Scalar, + X: &[E::Scalar], + ABC_Z: &mut R1CSResult, + ) -> Result<(), NovaError> { + if X.len() != self.num_io || W.len() != self.num_vars { + return Err(NovaError::InvalidWitnessLength); + } + + let R1CSResult { AZ, BZ, CZ } = ABC_Z; + + rayon::join( + || self.A.multiply_witness_into(W, u, X, AZ), + || { + rayon::join( + || self.B.multiply_witness_into(W, u, X, BZ), + || self.C.multiply_witness_into(W, u, X, CZ), + ) + }, + ); + + Ok(()) + } + /// Checks if the Relaxed R1CS instance is satisfiable given a witness and its shape pub fn is_sat_relaxed( &self, @@ -186,8 +243,7 @@ impl R1CSShape { // verify if Az * Bz = u*Cz + E let res_eq = { - let z = [W.W.clone(), vec![U.u], U.X.clone()].concat(); - let (Az, Bz, Cz) = self.multiply_vec(&z)?; + let (Az, Bz, Cz) = self.multiply_witness(&W.W, &U.u, &U.X)?; assert_eq!(Az.len(), self.num_cons); assert_eq!(Bz.len(), self.num_cons); assert_eq!(Cz.len(), self.num_cons); @@ -221,8 +277,7 @@ impl R1CSShape { // verify if Az * Bz = u*Cz let res_eq = { - let z = [W.W.clone(), vec![E::Scalar::ONE], U.X.clone()].concat(); - let (Az, Bz, Cz) = self.multiply_vec(&z)?; + let (Az, Bz, Cz) = self.multiply_witness(&W.W, &E::Scalar::ONE, &U.X)?; assert_eq!(Az.len(), self.num_cons); assert_eq!(Bz.len(), self.num_cons); assert_eq!(Cz.len(), self.num_cons); @@ -250,15 +305,9 @@ impl R1CSShape { U2: &R1CSInstance, W2: &R1CSWitness, ) -> Result<(Vec, Commitment), NovaError> { - let (AZ_1, BZ_1, CZ_1) = { - let Z1 = [W1.W.clone(), vec![U1.u], U1.X.clone()].concat(); - self.multiply_vec(&Z1)? - }; + let (AZ_1, BZ_1, CZ_1) = self.multiply_witness(&W1.W, &U1.u, &U1.X)?; - let (AZ_2, BZ_2, CZ_2) = { - let Z2 = [W2.W.clone(), vec![E::Scalar::ONE], U2.X.clone()].concat(); - self.multiply_vec(&Z2)? - }; + let (AZ_2, BZ_2, CZ_2) = self.multiply_witness(&W2.W, &E::Scalar::ONE, &U2.X)?; let (AZ_1_circ_BZ_2, AZ_2_circ_BZ_1, u_1_cdot_CZ_2, u_2_cdot_CZ_1) = { let AZ_1_circ_BZ_2 = (0..AZ_1.len()) @@ -293,7 +342,54 @@ impl R1CSShape { Ok((T, comm_T)) } - /// Pads the `R1CSShape` so that the shape passes `is_regular_shape` + /// A method to compute a commitment to the cross-term `T` given a + /// Relaxed R1CS instance-witness pair and an R1CS instance-witness pair + /// + /// This is [`R1CSShape::commit_T`] but into a buffer. + pub fn commit_T_into( + &self, + ck: &CommitmentKey, + U1: &RelaxedR1CSInstance, + W1: &RelaxedR1CSWitness, + U2: &R1CSInstance, + W2: &R1CSWitness, + T: &mut Vec, + ABC_Z_1: &mut R1CSResult, + ABC_Z_2: &mut R1CSResult, + ) -> Result, NovaError> { + self.multiply_witness_into(&W1.W, &U1.u, &U1.X, ABC_Z_1)?; + + let R1CSResult { + AZ: AZ_1, + BZ: BZ_1, + CZ: CZ_1, + } = ABC_Z_1; + + self.multiply_witness_into(&W2.W, &E::Scalar::ONE, &U2.X, ABC_Z_2)?; + + let R1CSResult { + AZ: AZ_2, + BZ: BZ_2, + CZ: CZ_2, + } = ABC_Z_2; + + // this doesn't allocate memory but has bad temporal cache locality -- should test to see which is faster + T.clear(); + + (0..AZ_1.len()) + .into_par_iter() + .map(|i| { + let AZ_1_circ_BZ_2 = AZ_1[i] * BZ_2[i]; + let AZ_2_circ_BZ_1 = AZ_2[i] * BZ_1[i]; + let u_1_cdot_Cz_2_plus_Cz_1 = U1.u * CZ_2[i] + CZ_1[i]; + AZ_1_circ_BZ_2 + AZ_2_circ_BZ_1 - u_1_cdot_Cz_2_plus_Cz_1 + }) + .collect_into_vec(T); + + Ok(CE::::commit(ck, T)) + } + + /// /// Pads the `R1CSShape` so that the shape passes `is_regular_shape` /// Renumbers variables to accommodate padded variables pub fn pad(&self) -> Self { // check if the provided R1CSShape is already as required @@ -355,13 +451,24 @@ impl R1CSShape { } } +impl R1CSResult { + /// Produces a default `R1CSResult` given an `R1CSShape` + pub fn default(S: &R1CSShape) -> R1CSResult { + R1CSResult { + AZ: vec![E::Scalar::ZERO; S.num_cons], + BZ: vec![E::Scalar::ZERO; S.num_cons], + CZ: vec![E::Scalar::ZERO; S.num_cons], + } + } +} + impl R1CSWitness { /// A method to create a witness object using a vector of scalars - pub fn new(S: &R1CSShape, W: &[E::Scalar]) -> Result, NovaError> { + pub fn new(S: &R1CSShape, W: Vec) -> Result, NovaError> { if S.num_vars != W.len() { Err(NovaError::InvalidWitnessLength) } else { - Ok(R1CSWitness { W: W.to_owned() }) + Ok(R1CSWitness { W }) } } @@ -375,16 +482,13 @@ impl R1CSInstance { /// A method to create an instance object using constituent elements pub fn new( S: &R1CSShape, - comm_W: &Commitment, - X: &[E::Scalar], + comm_W: Commitment, + X: Vec, ) -> Result, NovaError> { if S.num_io != X.len() { Err(NovaError::InvalidInputLength) } else { - Ok(R1CSInstance { - comm_W: *comm_W, - X: X.to_owned(), - }) + Ok(R1CSInstance { comm_W, X }) } } } @@ -408,9 +512,9 @@ impl RelaxedR1CSWitness { } /// Initializes a new `RelaxedR1CSWitness` from an `R1CSWitness` - pub fn from_r1cs_witness(S: &R1CSShape, witness: &R1CSWitness) -> RelaxedR1CSWitness { + pub fn from_r1cs_witness(S: &R1CSShape, witness: R1CSWitness) -> RelaxedR1CSWitness { RelaxedR1CSWitness { - W: witness.W.clone(), + W: witness.W, E: vec![E::Scalar::ZERO; S.num_cons], } } @@ -447,6 +551,31 @@ impl RelaxedR1CSWitness { Ok(RelaxedR1CSWitness { W, E }) } + /// Mutably folds an incoming `R1CSWitness` into the current one + pub fn fold_mut( + &mut self, + W2: &R1CSWitness, + T: &[E::Scalar], + r: &E::Scalar, + ) -> Result<(), NovaError> { + if self.W.len() != W2.W.len() { + return Err(NovaError::InvalidWitnessLength); + } + + self + .W + .par_iter_mut() + .zip_eq(&W2.W) + .for_each(|(a, b)| *a += *r * *b); + self + .E + .par_iter_mut() + .zip_eq(T) + .for_each(|(a, b)| *a += *r * *b); + + Ok(()) + } + /// Pads the provided witness to the correct length pub fn pad(&self, S: &R1CSShape) -> RelaxedR1CSWitness { let mut W = self.W.clone(); @@ -473,15 +602,18 @@ impl RelaxedR1CSInstance { /// Initializes a new `RelaxedR1CSInstance` from an `R1CSInstance` pub fn from_r1cs_instance( - ck: &CommitmentKey, + _ck: &CommitmentKey, S: &R1CSShape, - instance: &R1CSInstance, + instance: R1CSInstance, ) -> RelaxedR1CSInstance { - let mut r_instance = RelaxedR1CSInstance::default(ck, S); - r_instance.comm_W = instance.comm_W; - r_instance.u = E::Scalar::ONE; - r_instance.X = instance.X.clone(); - r_instance + assert_eq!(S.num_io, instance.X.len()); + + RelaxedR1CSInstance { + comm_W: instance.comm_W, + comm_E: Commitment::::default(), + u: E::Scalar::ONE, + X: instance.X, + } } /// Initializes a new `RelaxedR1CSInstance` from an `R1CSInstance` @@ -525,6 +657,19 @@ impl RelaxedR1CSInstance { u, } } + + /// Mutably folds an incoming `RelaxedR1CSInstance` into the current one + pub fn fold_mut(&mut self, U2: &R1CSInstance, comm_T: &Commitment, r: &E::Scalar) { + let (X2, comm_W_2) = (&U2.X, &U2.comm_W); + + // weighted sum of X, comm_W, comm_E, and u + self.X.par_iter_mut().zip_eq(X2).for_each(|(a, b)| { + *a += *r * *b; + }); + self.comm_W = self.comm_W + *comm_W_2 * *r; + self.comm_E = self.comm_E + *comm_T * *r; + self.u += *r; + } } impl TranscriptReprTrait for RelaxedR1CSInstance { @@ -555,6 +700,11 @@ impl AbsorbInROTrait for RelaxedR1CSInstance { } } +/// Empty buffer for `commit_T_into` +pub fn default_T(shape: &R1CSShape) -> Vec { + Vec::with_capacity(shape.num_cons) +} + #[cfg(test)] mod tests { use ff::Field; diff --git a/src/r1cs/sparse.rs b/src/r1cs/sparse.rs index 075b158d..7c6f3e1c 100644 --- a/src/r1cs/sparse.rs +++ b/src/r1cs/sparse.rs @@ -6,6 +6,7 @@ use ff::PrimeField; use rayon::prelude::*; use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; /// CSR format sparse matrix, We follow the names used by scipy. /// Detailed explanation here: https://stackoverflow.com/questions/52299420/scipy-csr-matrix-understand-indptr @@ -74,14 +75,14 @@ impl SparseMatrix { .zip(&self.indices[ptrs[0]..ptrs[1]]) } - /// Multiply by a dense vector; uses rayon/gpu. + /// Multiply by a dense vector; uses rayon to parallelize. pub fn multiply_vec(&self, vector: &[F]) -> Vec { assert_eq!(self.cols, vector.len(), "invalid shape"); self.multiply_vec_unchecked(vector) } - /// Multiply by a dense vector; uses rayon/gpu. + /// Multiply by a dense vector; uses rayon to parallelize. /// This does not check that the shape of the matrix/vector are compatible. pub fn multiply_vec_unchecked(&self, vector: &[F]) -> Vec { self @@ -96,6 +97,51 @@ impl SparseMatrix { .collect() } + /// Multiply by a witness representing a dense vector; uses rayon to parallelize. + pub fn multiply_witness(&self, W: &[F], u: &F, X: &[F]) -> Vec { + assert_eq!(self.cols, W.len() + X.len() + 1, "invalid shape"); + + self.multiply_witness_unchecked(W, u, X) + } + + /// Multiply by a witness representing a dense vector; uses rayon to parallelize. + /// This does not check that the shape of the matrix/vector are compatible. + pub fn multiply_witness_unchecked(&self, W: &[F], u: &F, X: &[F]) -> Vec { + // preallocate the result vector + let mut sink = Vec::with_capacity(self.indptr.len() - 1); + self.multiply_witness_into_unchecked(W, u, X, &mut sink); + sink + } + + /// Multiply by a witness representing a dense vector; uses rayon to parallelize. + pub fn multiply_witness_into(&self, W: &[F], u: &F, X: &[F], sink: &mut Vec) { + assert_eq!(self.cols, W.len() + X.len() + 1, "invalid shape"); + + self.multiply_witness_into_unchecked(W, u, X, sink); + } + + /// Multiply by a witness representing a dense vector; uses rayon to parallelize. + /// This does not check that the shape of the matrix/vector are compatible. + pub fn multiply_witness_into_unchecked(&self, W: &[F], u: &F, X: &[F], sink: &mut Vec) { + let num_vars = W.len(); + self + .indptr + .par_windows(2) + .map(|ptrs| { + self + .get_row_unchecked(ptrs.try_into().unwrap()) + .fold(F::ZERO, |acc, (val, col_idx)| { + let val = match col_idx.cmp(&num_vars) { + Ordering::Less => *val * W[*col_idx], + Ordering::Equal => *val * *u, + Ordering::Greater => *val * X[*col_idx - num_vars - 1], + }; + acc + val + }) + }) + .collect_into_vec(sink); + } + /// number of non-zero entries pub fn len(&self) -> usize { *self.indptr.last().unwrap() diff --git a/src/spartan/direct.rs b/src/spartan/direct.rs index 80986a2a..0f6c7096 100644 --- a/src/spartan/direct.rs +++ b/src/spartan/direct.rs @@ -137,7 +137,7 @@ impl, C: StepCircuit> DirectSN // convert the instance and witness to relaxed form let (u_relaxed, w_relaxed) = ( RelaxedR1CSInstance::from_r1cs_instance_unchecked(&u.comm_W, &u.X), - RelaxedR1CSWitness::from_r1cs_witness(&pk.S, &w), + RelaxedR1CSWitness::from_r1cs_witness(&pk.S, w), ); // prove the instance using Spartan