From 4fe94065900deff1016fb5087df4d8b5d8348dc3 Mon Sep 17 00:00:00 2001 From: Samuel Judson Date: Wed, 31 Jan 2024 11:51:39 -0500 Subject: [PATCH 01/26] Initial CCS implementation. --- nova/src/ccs/mod.rs | 618 +++++++++++++++++++++++++++++++++++++ nova/src/ccs/vector_ops.rs | 33 ++ nova/src/lib.rs | 2 + nova/src/r1cs/mod.rs | 4 +- nova/src/r1cs/sparse.rs | 2 +- nova/src/sparse.rs | 205 ++++++++++++ 6 files changed, 860 insertions(+), 4 deletions(-) create mode 100644 nova/src/ccs/mod.rs create mode 100644 nova/src/ccs/vector_ops.rs create mode 100644 nova/src/sparse.rs diff --git a/nova/src/ccs/mod.rs b/nova/src/ccs/mod.rs new file mode 100644 index 000000000..d26096bba --- /dev/null +++ b/nova/src/ccs/mod.rs @@ -0,0 +1,618 @@ +use ark_std::fmt; + +use ark_crypto_primitives::sponge::Absorb; +use ark_ec::{AdditiveGroup, CurveGroup}; +use ark_ff::{Field, PrimeField}; +use ark_relations::r1cs::ConstraintSystemRef; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; + +use ark_std::Zero; +use std::ops::Neg; + +#[cfg(feature = "parallel")] +use rayon::iter::{IntoParallelIterator, ParallelIterator}; + +use super::{absorb::AbsorbNonNative, commitment::CommitmentScheme}; + +pub use super::sparse::{MatrixRef, SparseMatrix}; + +mod vector_ops; +use vector_ops::{elem_add, elem_mul, scalar_mul}; + +use super::r1cs::R1CSShape; +pub use ark_relations::r1cs::Matrix; + +pub type VMatrixRef<'a, F> = &'a Vec>; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum Error { + ConstraintNumberMismatch, + InputLengthMismatch, + InvalidWitnessLength, + InvalidInputLength, + InvalidConversion, + InvalidMultiset, + MultisetCardinalityMismatch, + NotSatisfied, +} + +/// A trait capturing that the type encodes a constraint matrix +pub trait ConstraintMatrix { + fn validate(&self, num_constraints: usize, num_vars: usize, num_io: usize) + -> Result<(), Error>; + fn sparsify(&self, rows: usize, columns: usize) -> SparseMatrix; +} + +/// A (macro'd) implementation of the ConstraintMatrix trait for explicitly specified matricies +macro_rules! impl_Explicit_Constraint_Matrix { + (for $($t:ty),+) => { + $(impl ConstraintMatrix for $t { + fn validate(&self, num_constraints: usize, num_vars: usize, num_io: usize) -> Result<(), Error> { + for (i, row) in self.iter().enumerate() { + for (_value, j) in row { + if i >= num_constraints { + return Err(Error::ConstraintNumberMismatch); + } + if *j >= num_io + num_vars { + return Err(Error::InputLengthMismatch); + } + } + } + + Ok(()) + } + + fn sparsify(&self, rows: usize, columns: usize) -> SparseMatrix { + SparseMatrix::new(self, rows, columns) + } + })* + } +} +impl_Explicit_Constraint_Matrix!(for MatrixRef<'_, F>, VMatrixRef<'_, F>); + +/// An implementation of the ConstraintMatrix trait for sparse matricies +impl ConstraintMatrix for SparseMatrix { + fn validate( + &self, + _num_constraints: usize, + _num_vars: usize, + _num_io: usize, + ) -> Result<(), Error> { + Ok(()) + } + + fn sparsify(&self, _rows: usize, _columns: usize) -> SparseMatrix { + self.clone() + } +} + +/// A type that holds the shape of the CCS matrices +#[derive(Debug, Clone, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)] +pub struct CCSShape { + /// `m` in the Nova paper. + pub num_constraints: usize, + /// Witness length. + /// + /// `m - l - 1` in the Nova paper. + pub num_vars: usize, + /// Length of the public input `X`. It is expected to have a leading + /// `ScalarField::ONE` element, thus this field must be non-zero. + /// + /// `l + 1`, w.r.t. the Nova paper. + pub num_io: usize, + /// Number of matricies. + /// + /// `t` in the CCS paper. + pub num_matricies: usize, + /// Number of multisets + /// + /// `q` in the CCS paper. + pub num_multisets: usize, + /// Max cardinality of the multisets + /// + /// `d` in the CCS paper. + pub max_cardinality: usize, + pub Ms: Vec>, + pub Ss: Vec>, + pub cs: Vec, +} + +impl fmt::Display for CCSShape { + #[rustfmt::skip] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Ms = self.Ms.iter().map(|x| format!("[_, {}]", x.len())).collect::>().join(", "); + let Ss = self.Ss.iter().map(|x| format!("[_, {}]", x.len())).collect::>().join(", "); + let cs = self.cs.iter().map(|x| format!("{}", x)).collect::>().join(", "); + + write!(f, "CCSShape {{ num_constraints: {}, num_vars: {}, num_io: {}, num_matricies: {}, num_multisets: {}, max_cardinality: {}, Ms: {}, Ss: {}, cs: {} }}", + self.num_constraints, + self.num_vars, + self.num_io, + self.num_matricies, + self.num_multisets, + self.max_cardinality, + Ms, + Ss, + cs, + ) + } +} + +impl CCSShape { + /// Create an object of type `CCSShape` from the specified matricies and constant data structures + pub fn new>( + num_constraints: usize, + num_vars: usize, + num_io: usize, + num_matricies: usize, + num_multisets: usize, + max_cardinality: usize, + Ms: Vec, + Ss: Vec>, + cs: Vec, + ) -> Result, Error> { + if num_io == 0 { + return Err(Error::InvalidInputLength); + } + + Ms.iter() + .try_for_each(|M| M.validate(num_constraints, num_vars, num_io))?; + + assert_eq!(Ms.len(), num_matricies); + assert_eq!(Ss.len(), num_multisets); + assert_eq!(cs.len(), num_multisets); + + for S in Ss.iter() { + if S.len() > max_cardinality { + return Err(Error::MultisetCardinalityMismatch); + } + + S.iter().try_for_each(|idx| { + if idx >= &num_matricies { + Err(Error::InvalidMultiset) + } else { + Ok(()) + } + })?; + } + + let rows = num_constraints; + let columns = num_io + num_vars; + + let sMs = Ms + .iter() + .map(|M| M.sparsify(rows, columns)) + .collect::>>(); + + Ok(Self { + num_constraints, + num_io, + num_vars, + num_matricies, + num_multisets, + max_cardinality, + Ms: sMs, + Ss, + cs, + }) + } + + /// Checks if the CCS instance together with the witness `W` satisfies the CCS constraints determined by `shape`. + pub fn is_satisfied>( + &self, + U: &CCSInstance, + W: &CCSWitness, + pp: &C::PP, + ) -> Result<(), Error> { + assert_eq!(W.W.len(), self.num_vars); + assert_eq!(U.X.len(), self.num_io); + + let z = [U.X.as_slice(), W.W.as_slice()].concat(); + + let mut acc = vec![G::ScalarField::ZERO; self.num_constraints]; + + for i in 0..self.num_multisets { + let Ms_i: Vec<&SparseMatrix> = + self.Ss[i].iter().map(|j| &self.Ms[*j]).collect(); + + let hadamard_i: Vec = Ms_i.iter().fold( + vec![G::ScalarField::ONE; self.num_constraints], + |acc, M_j| elem_mul(&acc, &M_j.multiply_vec(&z)), + ); + + let res_i: Vec = scalar_mul(&hadamard_i, &self.cs[i]); + + acc = elem_add(&acc, &res_i); + } + + if ark_std::cfg_into_iter!(0..self.num_constraints).any(|idx| !acc[idx].is_zero()) { + return Err(Error::NotSatisfied); + } + + if U.commitment_W != C::commit(pp, &W.W) { + return Err(Error::NotSatisfied); + } + + Ok(()) + } +} + +/// Create an object of type `CCSShape` from the specified R1CS shape +impl From> for CCSShape { + fn from(shape: R1CSShape) -> Self { + Self { + num_constraints: shape.num_constraints, + num_io: shape.num_io, + num_vars: shape.num_vars, + num_matricies: 3, + num_multisets: 2, + max_cardinality: 2, + Ms: vec![shape.A.clone(), shape.B.clone(), shape.C.clone()], + Ss: vec![vec![0, 1], vec![2]], + cs: vec![G::ScalarField::ONE, G::ScalarField::ONE.neg()], + } + } +} + +impl From> for CCSShape { + fn from(cs: ConstraintSystemRef) -> Self { + assert!(cs.should_construct_matrices()); + let matrices = cs.to_matrices().unwrap(); + + let num_constraints = cs.num_constraints(); + let num_vars = cs.num_witness_variables(); + let num_io = cs.num_instance_variables(); + + let rows = num_constraints; + let columns = num_io + num_vars; + Self { + num_constraints, + num_io, + num_vars, + num_matricies: 3, + num_multisets: 2, + max_cardinality: 2, + Ms: vec![ + SparseMatrix::new(&matrices.a, rows, columns), + SparseMatrix::new(&matrices.b, rows, columns), + SparseMatrix::new(&matrices.c, rows, columns), + ], + Ss: vec![vec![0, 1], vec![2]], + cs: vec![G::ScalarField::ONE, G::ScalarField::ONE.neg()], + } + } +} + +/// A type that holds a witness for a given CCS instance. +#[derive(Clone, Debug, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)] +pub struct CCSWitness { + pub W: Vec, +} + +/// A type that holds an CCS instance. +#[derive(CanonicalSerialize, CanonicalDeserialize)] +pub struct CCSInstance> { + /// Commitment to witness. + pub commitment_W: C::Commitment, + /// X is assumed to start with a `ScalarField::ONE`. + pub X: Vec, +} + +impl> Clone for CCSInstance { + fn clone(&self) -> Self { + Self { + commitment_W: self.commitment_W, + X: self.X.clone(), + } + } +} + +impl> fmt::Debug for CCSInstance +where + C::Commitment: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CCSInstance") + .field("commitment_W", &self.commitment_W) + .field("X", &self.X) + .finish() + } +} + +impl> PartialEq for CCSInstance { + fn eq(&self, other: &Self) -> bool { + self.commitment_W == other.commitment_W && self.X == other.X + } +} + +impl> Eq for CCSInstance where C::Commitment: Eq {} + +impl Absorb for CCSInstance +where + G: CurveGroup + AbsorbNonNative, + G::ScalarField: Absorb, + C: CommitmentScheme, + C::Commitment: Into, +{ + fn to_sponge_bytes(&self, _: &mut Vec) { + unreachable!() + } + + fn to_sponge_field_elements(&self, dest: &mut Vec) { + >::to_sponge_field_elements( + &self.commitment_W.into(), + dest, + ); + + (&self.X[1..]).to_sponge_field_elements(dest); + } +} + +impl CCSWitness { + /// A method to create a witness object using a vector of scalars. + pub fn new(shape: &CCSShape, W: &[G::ScalarField]) -> Result { + if shape.num_vars != W.len() { + Err(Error::InvalidWitnessLength) + } else { + Ok(Self { W: W.to_owned() }) + } + } + + pub fn zero(shape: &CCSShape) -> Self { + Self { + W: vec![G::ScalarField::ZERO; shape.num_vars], + } + } + + /// Commits to the witness using the supplied generators + pub fn commit>(&self, pp: &C::PP) -> C::Commitment { + C::commit(pp, &self.W) + } +} + +impl> CCSInstance { + /// A method to create an instance object using constituent elements. + pub fn new( + shape: &CCSShape, + commitment_W: &C::Commitment, + X: &[G::ScalarField], + ) -> Result { + if X.is_empty() { + return Err(Error::InvalidInputLength); + } + if shape.num_io != X.len() { + Err(Error::InvalidInputLength) + } else { + Ok(Self { + commitment_W: *commitment_W, + X: X.to_owned(), + }) + } + } +} + +#[cfg(test)] +mod tests { + #![allow(non_upper_case_globals)] + #![allow(clippy::needless_range_loop)] + + use super::*; + use crate::pedersen::PedersenCommitment; + + use ark_relations::r1cs::Matrix; + use ark_test_curves::bls12_381::G1Projective as G; + + fn to_field_sparse(matrix: &[&[u64]]) -> Matrix { + let mut coo_matrix = Matrix::new(); + + for row in matrix { + let mut sparse_row = Vec::new(); + for (j, &f) in row.iter().enumerate() { + if f == 0 { + continue; + } + sparse_row.push((G::ScalarField::from(f), j)); + } + coo_matrix.push(sparse_row); + } + + coo_matrix + } + + fn to_field_elements(x: &[i64]) -> Vec { + x.iter().copied().map(G::ScalarField::from).collect() + } + + #[test] + #[rustfmt::skip] + fn invalid_input() { + let a = { + let a: &[&[u64]] = &[ + &[1, 2, 3], + &[3, 4, 5], + &[6, 7, 8], + ]; + to_field_sparse::(a) + }; + + assert_eq!( + CCSShape::::new(2, 2, 2, 3, 2, 2, vec![&a, &a, &a], vec![vec![0, 1], vec![2]], to_field_elements::(&[1, -1])), + Err(Error::ConstraintNumberMismatch) + ); + assert_eq!( + CCSShape::::new(3, 0, 1, 3, 2, 2, vec![&a, &a, &a], vec![vec![0, 1], vec![2]], to_field_elements::(&[1, -1])), + Err(Error::InputLengthMismatch) + ); + assert_eq!( + CCSShape::::new(3, 1, 2, 3, 1, 2, vec![&a, &a, &a], vec![vec![0, 1, 2]], to_field_elements::(&[1])), + Err(Error::MultisetCardinalityMismatch) + ); + assert_eq!( + CCSShape::::new(3, 1, 2, 3, 2, 2, vec![&a, &a, &a], vec![vec![3, 1], vec![2]], to_field_elements::(&[1, -1])), + Err(Error::InvalidMultiset) + ); + } + + #[test] + fn zero_instance_is_satisfied() -> Result<(), Error> { + #[rustfmt::skip] + let a = { + let a: &[&[u64]] = &[ + &[1, 2, 3], + &[3, 4, 5], + &[6, 7, 8], + ]; + to_field_sparse::(a) + }; + + const NUM_CONSTRAINTS: usize = 3; + const NUM_WITNESS: usize = 1; + const NUM_PUBLIC: usize = 2; + + let pp = PedersenCommitment::::setup(NUM_WITNESS, &()); + let shape = CCSShape::::new( + NUM_CONSTRAINTS, + NUM_WITNESS, + NUM_PUBLIC, + 3, + 2, + 2, + vec![&a, &a, &a], + vec![vec![0, 1], vec![2]], + to_field_elements::(&[1, -1]), + )?; + + let X = to_field_elements::(&[0, 0]); + let W = to_field_elements::(&[0]); + let commitment_W = PedersenCommitment::::commit(&pp, &W); + + let instance = CCSInstance::>::new(&shape, &commitment_W, &X)?; + let witness = CCSWitness::::new(&shape, &W)?; + + shape.is_satisfied(&instance, &witness, &pp)?; + Ok(()) + } + + #[test] + fn shape_conversion_from_r1cs() -> Result<(), Error> { + #[rustfmt::skip] + let a = { + let a: &[&[u64]] = &[ + &[1, 2, 3], + &[3, 4, 5], + &[6, 7, 8], + ]; + to_field_sparse::(a) + }; + + const NUM_CONSTRAINTS: usize = 3; + const NUM_WITNESS: usize = 1; + const NUM_PUBLIC: usize = 2; + + let pp = PedersenCommitment::::setup(NUM_WITNESS, &()); + let r1cs_shape: R1CSShape = + R1CSShape::::new(NUM_CONSTRAINTS, NUM_WITNESS, NUM_PUBLIC, &a, &a, &a).unwrap(); + + let ccs_shape = CCSShape::from(r1cs_shape); + + let X = to_field_elements::(&[0, 0]); + let W = to_field_elements::(&[0]); + let commitment_W = PedersenCommitment::::commit(&pp, &W); + + let instance = CCSInstance::>::new(&ccs_shape, &commitment_W, &X)?; + let witness = CCSWitness::::new(&ccs_shape, &W)?; + + ccs_shape.is_satisfied(&instance, &witness, &pp)?; + + Ok(()) + } + + // Example from Vitalik's blog for equation x**3 + x + 5 == 35. + // + // Note that our implementation shuffles columns such that witness comes first. + + const A: &[&[u64]] = &[ + &[0, 0, 1, 0, 0, 0], + &[0, 0, 0, 1, 0, 0], + &[0, 0, 1, 0, 1, 0], + &[5, 0, 0, 0, 0, 1], + ]; + const B: &[&[u64]] = &[ + &[0, 0, 1, 0, 0, 0], + &[0, 0, 1, 0, 0, 0], + &[1, 0, 0, 0, 0, 0], + &[1, 0, 0, 0, 0, 0], + ]; + const C: &[&[u64]] = &[ + &[0, 0, 0, 1, 0, 0], + &[0, 0, 0, 0, 1, 0], + &[0, 0, 0, 0, 0, 1], + &[0, 1, 0, 0, 0, 0], + ]; + + #[test] + fn is_satisfied() -> Result<(), Error> { + let (a, b, c) = { + ( + to_field_sparse::(A), + to_field_sparse::(B), + to_field_sparse::(C), + ) + }; + + const NUM_CONSTRAINTS: usize = 4; + const NUM_WITNESS: usize = 4; + const NUM_PUBLIC: usize = 2; + + let pp = PedersenCommitment::::setup(NUM_WITNESS, &()); + let shape = CCSShape::::new( + NUM_CONSTRAINTS, + NUM_WITNESS, + NUM_PUBLIC, + 3, + 2, + 2, + vec![&a, &b, &c], + vec![vec![0, 1], vec![2]], + to_field_elements::(&[1, -1]), + )?; + let X = to_field_elements::(&[1, 35]); + let W = to_field_elements::(&[3, 9, 27, 30]); + let commitment_W = PedersenCommitment::::commit(&pp, &W); + + let instance = CCSInstance::>::new(&shape, &commitment_W, &X)?; + let witness = CCSWitness::::new(&shape, &W)?; + + shape.is_satisfied(&instance, &witness, &pp)?; + + // Change commitment. + let invalid_commitment = commitment_W.double(); + let instance = + CCSInstance::>::new(&shape, &invalid_commitment, &X)?; + assert_eq!( + shape.is_satisfied(&instance, &witness, &pp), + Err(Error::NotSatisfied) + ); + + // Provide invalid witness. + let invalid_W = to_field_elements::(&[4, 9, 27, 30]); + let commitment_invalid_W = PedersenCommitment::::commit(&pp, &W); + let instance = + CCSInstance::>::new(&shape, &commitment_invalid_W, &X)?; + let invalid_witness = CCSWitness::::new(&shape, &invalid_W)?; + assert_eq!( + shape.is_satisfied(&instance, &invalid_witness, &pp), + Err(Error::NotSatisfied) + ); + + // Provide invalid public input. + let invalid_X = to_field_elements::(&[1, 36]); + let instance = + CCSInstance::>::new(&shape, &commitment_W, &invalid_X)?; + assert_eq!( + shape.is_satisfied(&instance, &witness, &pp), + Err(Error::NotSatisfied) + ); + Ok(()) + } +} diff --git a/nova/src/ccs/vector_ops.rs b/nova/src/ccs/vector_ops.rs new file mode 100644 index 000000000..19a20cae4 --- /dev/null +++ b/nova/src/ccs/vector_ops.rs @@ -0,0 +1,33 @@ +use ark_ff::PrimeField; +use ark_std::iter::zip; + +/// Multiply two vectors element wise (hadamard product). +pub fn elem_mul(us: &Vec, vs: &Vec) -> Vec { + assert_eq!(us.len(), vs.len()); + + elem_mul_unchecked(us, vs) +} + +/// Multiply two vectors element wise (hadamard product). +/// This does not check that the vector shapes are compatible. +pub fn elem_mul_unchecked(us: &Vec, vs: &Vec) -> Vec { + zip(us, vs).map(|(u, v)| *u * v).collect() +} + +/// Multiply a vector by a scalar. +pub fn scalar_mul(us: &[F], c: &F) -> Vec { + us.iter().map(|u| *u * c).collect() +} + +/// Add two vectors together element wise. +pub fn elem_add(us: &Vec, vs: &Vec) -> Vec { + assert_eq!(us.len(), vs.len()); + + elem_add_unchecked(us, vs) +} + +/// Add two vectors together element wise +/// This does not check that the vector shapes are compatible. +pub fn elem_add_unchecked(us: &Vec, vs: &Vec) -> Vec { + zip(us, vs).map(|(u, v)| *u + v).collect() +} diff --git a/nova/src/lib.rs b/nova/src/lib.rs index 7f3e92b4e..0c0d60185 100644 --- a/nova/src/lib.rs +++ b/nova/src/lib.rs @@ -6,6 +6,7 @@ mod absorb; mod provider; +mod sparse; mod utils; mod circuits; @@ -15,6 +16,7 @@ mod gadgets; #[cfg(test)] mod test_utils; +pub mod ccs; pub mod commitment; pub mod r1cs; diff --git a/nova/src/r1cs/mod.rs b/nova/src/r1cs/mod.rs index 3a5b4e220..a8541f2f5 100644 --- a/nova/src/r1cs/mod.rs +++ b/nova/src/r1cs/mod.rs @@ -14,11 +14,9 @@ use rayon::iter::{ use super::{absorb::AbsorbNonNative, commitment::CommitmentScheme}; -pub mod sparse; -pub use sparse::SparseMatrix; +pub use super::sparse::{MatrixRef, SparseMatrix}; pub use ark_relations::r1cs::Matrix; -pub type MatrixRef<'a, F> = &'a [Vec<(F, usize)>]; #[derive(Debug, Copy, Clone, PartialEq)] pub enum Error { diff --git a/nova/src/r1cs/sparse.rs b/nova/src/r1cs/sparse.rs index 5c81ea578..ef0660857 100644 --- a/nova/src/r1cs/sparse.rs +++ b/nova/src/r1cs/sparse.rs @@ -10,7 +10,7 @@ use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; #[cfg(feature = "parallel")] use rayon::{iter::ParallelIterator, slice::ParallelSlice}; -use super::MatrixRef; +pub type MatrixRef<'a, F> = &'a [Vec<(F, usize)>]; /// CSR format sparse matrix, We follow the names used by scipy. /// Detailed explanation here: https://stackoverflow.com/questions/52299420/scipy-csr-matrix-understand-indptr diff --git a/nova/src/sparse.rs b/nova/src/sparse.rs new file mode 100644 index 000000000..ef0660857 --- /dev/null +++ b/nova/src/sparse.rs @@ -0,0 +1,205 @@ +//! # Sparse Matrices +//! +//! This module defines a custom implementation of CSR/CSC sparse matrices. +//! Specifically, we implement sparse matrix / dense vector multiplication +//! to compute the `A z`, `B z`, and `C z` in Nova. + +use ark_ff::PrimeField; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; + +#[cfg(feature = "parallel")] +use rayon::{iter::ParallelIterator, slice::ParallelSlice}; + +pub type MatrixRef<'a, F> = &'a [Vec<(F, usize)>]; + +/// CSR format sparse matrix, We follow the names used by scipy. +/// Detailed explanation here: https://stackoverflow.com/questions/52299420/scipy-csr-matrix-understand-indptr +#[derive(Clone, Debug, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)] +pub struct SparseMatrix { + /// all non-zero values in the matrix + pub data: Vec, + /// column indices + pub indices: Vec, + /// row information + pub indptr: Vec, + /// number of columns + pub cols: usize, +} + +impl SparseMatrix { + /// 0x0 empty matrix + pub fn empty() -> Self { + SparseMatrix { + data: vec![], + indices: vec![], + indptr: vec![0], + cols: 0, + } + } + + /// Construct from the COO representation; + /// We assume that the rows are sorted during construction. + pub fn new(matrix: MatrixRef<'_, F>, rows: usize, cols: usize) -> Self { + let mut new_matrix = vec![vec![]; rows]; + let matrix_iter = matrix + .iter() + .enumerate() + .flat_map(|(i, row)| row.iter().map(move |&(f, j)| (i, j, f))); + for (row, col, val) in matrix_iter { + new_matrix[row].push((col, val)); + } + + for row in new_matrix.iter() { + assert!(row.windows(2).all(|w| w[0].0 < w[1].0)); + } + + let mut indptr = vec![0; rows + 1]; + for (i, row) in new_matrix.iter().enumerate() { + indptr[i + 1] = indptr[i] + row.len(); + } + + let mut indices = vec![]; + let mut data = vec![]; + for row in new_matrix { + let (idx, val): (Vec, Vec) = row.into_iter().unzip(); + indices.extend(idx); + data.extend(val); + } + + SparseMatrix { data, indices, indptr, cols } + } + + /// Retrieves the data for row slice [i..j] from `ptrs`. + /// We assume that `ptrs` is indexed from `indptrs` and do not check if the + /// returned slice is actually a valid row. + pub fn get_row_unchecked(&self, ptrs: &[usize; 2]) -> impl Iterator { + self.data[ptrs[0]..ptrs[1]] + .iter() + .zip(&self.indices[ptrs[0]..ptrs[1]]) + } + + /// Multiply by a dense vector; + pub fn multiply_vec(&self, vector: &[F]) -> Vec { + assert_eq!(self.cols, vector.len()); + + self.multiply_vec_unchecked(vector) + } + + /// Multiply by a dense vector; + /// This does not check that the shape of the matrix/vector are compatible. + pub fn multiply_vec_unchecked(&self, vector: &[F]) -> Vec { + #[cfg(feature = "parallel")] + let iter = self.indptr.par_windows(2); + #[cfg(not(feature = "parallel"))] + let iter = self.indptr.windows(2); + + iter.map(|ptrs| { + self.get_row_unchecked(ptrs.try_into().unwrap()) + .map(|(val, col_idx)| *val * vector[*col_idx]) + .sum() + }) + .collect() + } + + /// number of non-zero entries + pub fn len(&self) -> usize { + *self.indptr.last().unwrap() + } + + /// empty matrix + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// returns a custom iterator + pub fn iter(&self) -> Iter<'_, F> { + let mut row = 0; + while self.indptr[row + 1] == 0 { + row += 1; + } + Iter { + matrix: self, + row, + i: 0, + nnz: *self.indptr.last().unwrap(), + } + } +} + +/// Iterator for sparse matrix +pub struct Iter<'a, F: PrimeField> { + matrix: &'a SparseMatrix, + row: usize, + i: usize, + nnz: usize, +} + +impl<'a, F: PrimeField> Iterator for Iter<'a, F> { + type Item = (usize, usize, F); + + fn next(&mut self) -> Option { + // are we at the end? + if self.i == self.nnz { + return None; + } + + // compute current item + let curr_item = ( + self.row, + self.matrix.indices[self.i], + self.matrix.data[self.i], + ); + + // advance the iterator + self.i += 1; + // edge case at the end + if self.i == self.nnz { + return Some(curr_item); + } + // if `i` has moved to next row + while self.i >= self.matrix.indptr[self.row + 1] { + self.row += 1; + } + + Some(curr_item) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + type Fr = ark_test_curves::bls12_381::Fr; + + #[test] + fn test_matrix_creation() { + let matrix_data = vec![ + vec![(Fr::from(2), 1)], + vec![(Fr::from(3), 2)], + vec![(Fr::from(4), 0)], + ]; + let sparse_matrix = SparseMatrix::::new(&matrix_data, 3, 3); + + assert_eq!( + sparse_matrix.data, + vec![Fr::from(2), Fr::from(3), Fr::from(4)] + ); + assert_eq!(sparse_matrix.indices, vec![1, 2, 0]); + assert_eq!(sparse_matrix.indptr, vec![0, 1, 2, 3]); + } + + #[test] + fn test_matrix_vector_multiplication() { + let matrix_data = vec![ + vec![(Fr::from(2), 1), (Fr::from(7), 2)], + vec![(Fr::from(3), 2)], + vec![(Fr::from(4), 0)], + ]; + let sparse_matrix = SparseMatrix::::new(&matrix_data, 3, 3); + let vector = vec![Fr::from(1), Fr::from(2), Fr::from(3)]; + + let result = sparse_matrix.multiply_vec(&vector); + + assert_eq!(result, vec![Fr::from(25), Fr::from(9), Fr::from(4)]); + } +} From fd5df57fc8752208ac9fe2a1eba8c28a0a8b74fa Mon Sep 17 00:00:00 2001 From: Samuel Judson Date: Thu, 1 Feb 2024 14:06:53 -0500 Subject: [PATCH 02/26] Remove direct construction interfaces so that everything goes through R1CS. --- nova/src/ccs/mod.rs | 323 ++++--------------------------------------- nova/src/r1cs/mod.rs | 12 +- 2 files changed, 36 insertions(+), 299 deletions(-) diff --git a/nova/src/ccs/mod.rs b/nova/src/ccs/mod.rs index d26096bba..9eb0ce549 100644 --- a/nova/src/ccs/mod.rs +++ b/nova/src/ccs/mod.rs @@ -1,8 +1,5 @@ -use ark_std::fmt; - -use ark_crypto_primitives::sponge::Absorb; use ark_ec::{AdditiveGroup, CurveGroup}; -use ark_ff::{Field, PrimeField}; +use ark_ff::Field; use ark_relations::r1cs::ConstraintSystemRef; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; @@ -12,7 +9,7 @@ use std::ops::Neg; #[cfg(feature = "parallel")] use rayon::iter::{IntoParallelIterator, ParallelIterator}; -use super::{absorb::AbsorbNonNative, commitment::CommitmentScheme}; +use super::commitment::CommitmentScheme; pub use super::sparse::{MatrixRef, SparseMatrix}; @@ -20,9 +17,6 @@ mod vector_ops; use vector_ops::{elem_add, elem_mul, scalar_mul}; use super::r1cs::R1CSShape; -pub use ark_relations::r1cs::Matrix; - -pub type VMatrixRef<'a, F> = &'a Vec>; #[derive(Debug, Copy, Clone, PartialEq)] pub enum Error { @@ -36,118 +30,47 @@ pub enum Error { NotSatisfied, } -/// A trait capturing that the type encodes a constraint matrix -pub trait ConstraintMatrix { - fn validate(&self, num_constraints: usize, num_vars: usize, num_io: usize) - -> Result<(), Error>; - fn sparsify(&self, rows: usize, columns: usize) -> SparseMatrix; -} - -/// A (macro'd) implementation of the ConstraintMatrix trait for explicitly specified matricies -macro_rules! impl_Explicit_Constraint_Matrix { - (for $($t:ty),+) => { - $(impl ConstraintMatrix for $t { - fn validate(&self, num_constraints: usize, num_vars: usize, num_io: usize) -> Result<(), Error> { - for (i, row) in self.iter().enumerate() { - for (_value, j) in row { - if i >= num_constraints { - return Err(Error::ConstraintNumberMismatch); - } - if *j >= num_io + num_vars { - return Err(Error::InputLengthMismatch); - } - } - } - - Ok(()) - } - - fn sparsify(&self, rows: usize, columns: usize) -> SparseMatrix { - SparseMatrix::new(self, rows, columns) - } - })* - } -} -impl_Explicit_Constraint_Matrix!(for MatrixRef<'_, F>, VMatrixRef<'_, F>); - -/// An implementation of the ConstraintMatrix trait for sparse matricies -impl ConstraintMatrix for SparseMatrix { - fn validate( - &self, - _num_constraints: usize, - _num_vars: usize, - _num_io: usize, - ) -> Result<(), Error> { - Ok(()) - } - - fn sparsify(&self, _rows: usize, _columns: usize) -> SparseMatrix { - self.clone() - } -} - /// A type that holds the shape of the CCS matrices #[derive(Debug, Clone, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)] pub struct CCSShape { - /// `m` in the Nova paper. + /// `m` in the CCS/HyperNova papers. pub num_constraints: usize, /// Witness length. /// - /// `m - l - 1` in the Nova paper. + /// `m - l - 1` in the CCS/HyperNova papers. pub num_vars: usize, /// Length of the public input `X`. It is expected to have a leading /// `ScalarField::ONE` element, thus this field must be non-zero. /// - /// `l + 1`, w.r.t. the Nova paper. + /// `l + 1`, w.r.t. the CCS/HyperNova papers. pub num_io: usize, /// Number of matricies. /// - /// `t` in the CCS paper. + /// `t` in the CCS/HyperNova papers. pub num_matricies: usize, /// Number of multisets /// - /// `q` in the CCS paper. + /// `q` in the CCS/HyperNova papers. pub num_multisets: usize, /// Max cardinality of the multisets /// - /// `d` in the CCS paper. + /// `d` in the CCS/HyperNova papers. pub max_cardinality: usize, pub Ms: Vec>, pub Ss: Vec>, pub cs: Vec, } -impl fmt::Display for CCSShape { - #[rustfmt::skip] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let Ms = self.Ms.iter().map(|x| format!("[_, {}]", x.len())).collect::>().join(", "); - let Ss = self.Ss.iter().map(|x| format!("[_, {}]", x.len())).collect::>().join(", "); - let cs = self.cs.iter().map(|x| format!("{}", x)).collect::>().join(", "); - - write!(f, "CCSShape {{ num_constraints: {}, num_vars: {}, num_io: {}, num_matricies: {}, num_multisets: {}, max_cardinality: {}, Ms: {}, Ss: {}, cs: {} }}", - self.num_constraints, - self.num_vars, - self.num_io, - self.num_matricies, - self.num_multisets, - self.max_cardinality, - Ms, - Ss, - cs, - ) - } -} - impl CCSShape { /// Create an object of type `CCSShape` from the specified matricies and constant data structures - pub fn new>( + pub fn new( num_constraints: usize, num_vars: usize, num_io: usize, num_matricies: usize, num_multisets: usize, max_cardinality: usize, - Ms: Vec, + Ms: Vec>, Ss: Vec>, cs: Vec, ) -> Result, Error> { @@ -155,9 +78,6 @@ impl CCSShape { return Err(Error::InvalidInputLength); } - Ms.iter() - .try_for_each(|M| M.validate(num_constraints, num_vars, num_io))?; - assert_eq!(Ms.len(), num_matricies); assert_eq!(Ss.len(), num_multisets); assert_eq!(cs.len(), num_multisets); @@ -176,14 +96,6 @@ impl CCSShape { })?; } - let rows = num_constraints; - let columns = num_io + num_vars; - - let sMs = Ms - .iter() - .map(|M| M.sparsify(rows, columns)) - .collect::>>(); - Ok(Self { num_constraints, num_io, @@ -191,7 +103,7 @@ impl CCSShape { num_matricies, num_multisets, max_cardinality, - Ms: sMs, + Ms, Ss, cs, }) @@ -247,7 +159,7 @@ impl From> for CCSShape { num_matricies: 3, num_multisets: 2, max_cardinality: 2, - Ms: vec![shape.A.clone(), shape.B.clone(), shape.C.clone()], + Ms: vec![shape.A, shape.B, shape.C], Ss: vec![vec![0, 1], vec![2]], cs: vec![G::ScalarField::ONE, G::ScalarField::ONE.neg()], } @@ -256,30 +168,8 @@ impl From> for CCSShape { impl From> for CCSShape { fn from(cs: ConstraintSystemRef) -> Self { - assert!(cs.should_construct_matrices()); - let matrices = cs.to_matrices().unwrap(); - - let num_constraints = cs.num_constraints(); - let num_vars = cs.num_witness_variables(); - let num_io = cs.num_instance_variables(); - - let rows = num_constraints; - let columns = num_io + num_vars; - Self { - num_constraints, - num_io, - num_vars, - num_matricies: 3, - num_multisets: 2, - max_cardinality: 2, - Ms: vec![ - SparseMatrix::new(&matrices.a, rows, columns), - SparseMatrix::new(&matrices.b, rows, columns), - SparseMatrix::new(&matrices.c, rows, columns), - ], - Ss: vec![vec![0, 1], vec![2]], - cs: vec![G::ScalarField::ONE, G::ScalarField::ONE.neg()], - } + let shape = R1CSShape::from(cs); + shape.into() } } @@ -307,18 +197,6 @@ impl> Clone for CCSInstance { } } -impl> fmt::Debug for CCSInstance -where - C::Commitment: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("CCSInstance") - .field("commitment_W", &self.commitment_W) - .field("X", &self.X) - .finish() - } -} - impl> PartialEq for CCSInstance { fn eq(&self, other: &Self) -> bool { self.commitment_W == other.commitment_W && self.X == other.X @@ -327,27 +205,6 @@ impl> PartialEq for CCSInstance { impl> Eq for CCSInstance where C::Commitment: Eq {} -impl Absorb for CCSInstance -where - G: CurveGroup + AbsorbNonNative, - G::ScalarField: Absorb, - C: CommitmentScheme, - C::Commitment: Into, -{ - fn to_sponge_bytes(&self, _: &mut Vec) { - unreachable!() - } - - fn to_sponge_field_elements(&self, dest: &mut Vec) { - >::to_sponge_field_elements( - &self.commitment_W.into(), - dest, - ); - - (&self.X[1..]).to_sponge_field_elements(dest); - } -} - impl CCSWitness { /// A method to create a witness object using a vector of scalars. pub fn new(shape: &CCSShape, W: &[G::ScalarField]) -> Result { @@ -399,59 +256,9 @@ mod tests { use super::*; use crate::pedersen::PedersenCommitment; - use ark_relations::r1cs::Matrix; use ark_test_curves::bls12_381::G1Projective as G; - fn to_field_sparse(matrix: &[&[u64]]) -> Matrix { - let mut coo_matrix = Matrix::new(); - - for row in matrix { - let mut sparse_row = Vec::new(); - for (j, &f) in row.iter().enumerate() { - if f == 0 { - continue; - } - sparse_row.push((G::ScalarField::from(f), j)); - } - coo_matrix.push(sparse_row); - } - - coo_matrix - } - - fn to_field_elements(x: &[i64]) -> Vec { - x.iter().copied().map(G::ScalarField::from).collect() - } - - #[test] - #[rustfmt::skip] - fn invalid_input() { - let a = { - let a: &[&[u64]] = &[ - &[1, 2, 3], - &[3, 4, 5], - &[6, 7, 8], - ]; - to_field_sparse::(a) - }; - - assert_eq!( - CCSShape::::new(2, 2, 2, 3, 2, 2, vec![&a, &a, &a], vec![vec![0, 1], vec![2]], to_field_elements::(&[1, -1])), - Err(Error::ConstraintNumberMismatch) - ); - assert_eq!( - CCSShape::::new(3, 0, 1, 3, 2, 2, vec![&a, &a, &a], vec![vec![0, 1], vec![2]], to_field_elements::(&[1, -1])), - Err(Error::InputLengthMismatch) - ); - assert_eq!( - CCSShape::::new(3, 1, 2, 3, 1, 2, vec![&a, &a, &a], vec![vec![0, 1, 2]], to_field_elements::(&[1])), - Err(Error::MultisetCardinalityMismatch) - ); - assert_eq!( - CCSShape::::new(3, 1, 2, 3, 2, 2, vec![&a, &a, &a], vec![vec![3, 1], vec![2]], to_field_elements::(&[1, -1])), - Err(Error::InvalidMultiset) - ); - } + use crate::r1cs::tests::{to_field_elements, to_field_sparse, A, B, C}; #[test] fn zero_instance_is_satisfied() -> Result<(), Error> { @@ -469,46 +276,6 @@ mod tests { const NUM_WITNESS: usize = 1; const NUM_PUBLIC: usize = 2; - let pp = PedersenCommitment::::setup(NUM_WITNESS, &()); - let shape = CCSShape::::new( - NUM_CONSTRAINTS, - NUM_WITNESS, - NUM_PUBLIC, - 3, - 2, - 2, - vec![&a, &a, &a], - vec![vec![0, 1], vec![2]], - to_field_elements::(&[1, -1]), - )?; - - let X = to_field_elements::(&[0, 0]); - let W = to_field_elements::(&[0]); - let commitment_W = PedersenCommitment::::commit(&pp, &W); - - let instance = CCSInstance::>::new(&shape, &commitment_W, &X)?; - let witness = CCSWitness::::new(&shape, &W)?; - - shape.is_satisfied(&instance, &witness, &pp)?; - Ok(()) - } - - #[test] - fn shape_conversion_from_r1cs() -> Result<(), Error> { - #[rustfmt::skip] - let a = { - let a: &[&[u64]] = &[ - &[1, 2, 3], - &[3, 4, 5], - &[6, 7, 8], - ]; - to_field_sparse::(a) - }; - - const NUM_CONSTRAINTS: usize = 3; - const NUM_WITNESS: usize = 1; - const NUM_PUBLIC: usize = 2; - let pp = PedersenCommitment::::setup(NUM_WITNESS, &()); let r1cs_shape: R1CSShape = R1CSShape::::new(NUM_CONSTRAINTS, NUM_WITNESS, NUM_PUBLIC, &a, &a, &a).unwrap(); @@ -523,33 +290,9 @@ mod tests { let witness = CCSWitness::::new(&ccs_shape, &W)?; ccs_shape.is_satisfied(&instance, &witness, &pp)?; - Ok(()) } - // Example from Vitalik's blog for equation x**3 + x + 5 == 35. - // - // Note that our implementation shuffles columns such that witness comes first. - - const A: &[&[u64]] = &[ - &[0, 0, 1, 0, 0, 0], - &[0, 0, 0, 1, 0, 0], - &[0, 0, 1, 0, 1, 0], - &[5, 0, 0, 0, 0, 1], - ]; - const B: &[&[u64]] = &[ - &[0, 0, 1, 0, 0, 0], - &[0, 0, 1, 0, 0, 0], - &[1, 0, 0, 0, 0, 0], - &[1, 0, 0, 0, 0, 0], - ]; - const C: &[&[u64]] = &[ - &[0, 0, 0, 1, 0, 0], - &[0, 0, 0, 0, 1, 0], - &[0, 0, 0, 0, 0, 1], - &[0, 1, 0, 0, 0, 0], - ]; - #[test] fn is_satisfied() -> Result<(), Error> { let (a, b, c) = { @@ -565,32 +308,26 @@ mod tests { const NUM_PUBLIC: usize = 2; let pp = PedersenCommitment::::setup(NUM_WITNESS, &()); - let shape = CCSShape::::new( - NUM_CONSTRAINTS, - NUM_WITNESS, - NUM_PUBLIC, - 3, - 2, - 2, - vec![&a, &b, &c], - vec![vec![0, 1], vec![2]], - to_field_elements::(&[1, -1]), - )?; + let r1cs_shape: R1CSShape = + R1CSShape::::new(NUM_CONSTRAINTS, NUM_WITNESS, NUM_PUBLIC, &a, &b, &c).unwrap(); + + let ccs_shape = CCSShape::from(r1cs_shape); + let X = to_field_elements::(&[1, 35]); let W = to_field_elements::(&[3, 9, 27, 30]); let commitment_W = PedersenCommitment::::commit(&pp, &W); - let instance = CCSInstance::>::new(&shape, &commitment_W, &X)?; - let witness = CCSWitness::::new(&shape, &W)?; + let instance = CCSInstance::>::new(&ccs_shape, &commitment_W, &X)?; + let witness = CCSWitness::::new(&ccs_shape, &W)?; - shape.is_satisfied(&instance, &witness, &pp)?; + ccs_shape.is_satisfied(&instance, &witness, &pp)?; // Change commitment. let invalid_commitment = commitment_W.double(); let instance = - CCSInstance::>::new(&shape, &invalid_commitment, &X)?; + CCSInstance::>::new(&ccs_shape, &invalid_commitment, &X)?; assert_eq!( - shape.is_satisfied(&instance, &witness, &pp), + ccs_shape.is_satisfied(&instance, &witness, &pp), Err(Error::NotSatisfied) ); @@ -598,19 +335,19 @@ mod tests { let invalid_W = to_field_elements::(&[4, 9, 27, 30]); let commitment_invalid_W = PedersenCommitment::::commit(&pp, &W); let instance = - CCSInstance::>::new(&shape, &commitment_invalid_W, &X)?; - let invalid_witness = CCSWitness::::new(&shape, &invalid_W)?; + CCSInstance::>::new(&ccs_shape, &commitment_invalid_W, &X)?; + let invalid_witness = CCSWitness::::new(&ccs_shape, &invalid_W)?; assert_eq!( - shape.is_satisfied(&instance, &invalid_witness, &pp), + ccs_shape.is_satisfied(&instance, &invalid_witness, &pp), Err(Error::NotSatisfied) ); // Provide invalid public input. let invalid_X = to_field_elements::(&[1, 36]); let instance = - CCSInstance::>::new(&shape, &commitment_W, &invalid_X)?; + CCSInstance::>::new(&ccs_shape, &commitment_W, &invalid_X)?; assert_eq!( - shape.is_satisfied(&instance, &witness, &pp), + ccs_shape.is_satisfied(&instance, &witness, &pp), Err(Error::NotSatisfied) ); Ok(()) diff --git a/nova/src/r1cs/mod.rs b/nova/src/r1cs/mod.rs index a8541f2f5..c86f61962 100644 --- a/nova/src/r1cs/mod.rs +++ b/nova/src/r1cs/mod.rs @@ -615,7 +615,7 @@ pub fn commit_T_with_relaxed>( } #[cfg(test)] -mod tests { +pub(crate) mod tests { #![allow(non_upper_case_globals)] #![allow(clippy::needless_range_loop)] @@ -626,7 +626,7 @@ mod tests { use ark_relations::r1cs::Matrix; use ark_test_curves::bls12_381::{Fr as Scalar, G1Projective as G}; - fn to_field_sparse(matrix: &[&[u64]]) -> Matrix { + pub(crate) fn to_field_sparse(matrix: &[&[u64]]) -> Matrix { let mut coo_matrix = Matrix::new(); for row in matrix { @@ -643,7 +643,7 @@ mod tests { coo_matrix } - fn to_field_elements(x: &[u64]) -> Vec { + pub(crate) fn to_field_elements(x: &[i64]) -> Vec { x.iter().copied().map(G::ScalarField::from).collect() } @@ -689,19 +689,19 @@ mod tests { // // Note that our implementation shuffles columns such that witness comes first. - const A: &[&[u64]] = &[ + pub(crate) const A: &[&[u64]] = &[ &[0, 0, 1, 0, 0, 0], &[0, 0, 0, 1, 0, 0], &[0, 0, 1, 0, 1, 0], &[5, 0, 0, 0, 0, 1], ]; - const B: &[&[u64]] = &[ + pub(crate) const B: &[&[u64]] = &[ &[0, 0, 1, 0, 0, 0], &[0, 0, 1, 0, 0, 0], &[1, 0, 0, 0, 0, 0], &[1, 0, 0, 0, 0, 0], ]; - const C: &[&[u64]] = &[ + pub(crate) const C: &[&[u64]] = &[ &[0, 0, 0, 1, 0, 0], &[0, 0, 0, 0, 1, 0], &[0, 0, 0, 0, 0, 1], From 00ce2d9f362a7efda8dcdea560507a0269da46fd Mon Sep 17 00:00:00 2001 From: Samuel Judson Date: Thu, 1 Feb 2024 17:35:40 -0500 Subject: [PATCH 03/26] Trim more, Fold multipliers together, and inline satisfaction checking. --- nova/src/ccs/mod.rs | 78 +++++++++++++++++++-------------------------- 1 file changed, 32 insertions(+), 46 deletions(-) diff --git a/nova/src/ccs/mod.rs b/nova/src/ccs/mod.rs index 9eb0ce549..8f835fa9a 100644 --- a/nova/src/ccs/mod.rs +++ b/nova/src/ccs/mod.rs @@ -1,21 +1,17 @@ use ark_ec::{AdditiveGroup, CurveGroup}; use ark_ff::Field; -use ark_relations::r1cs::ConstraintSystemRef; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::Zero; use std::ops::Neg; #[cfg(feature = "parallel")] -use rayon::iter::{IntoParallelIterator, ParallelIterator}; +use rayon::iter::{ParallelIterator, IndexedParallelIterator, IntoParallelRefIterator, IntoParallelRefMutIterator}; use super::commitment::CommitmentScheme; pub use super::sparse::{MatrixRef, SparseMatrix}; -mod vector_ops; -use vector_ops::{elem_add, elem_mul, scalar_mul}; - use super::r1cs::R1CSShape; #[derive(Debug, Copy, Clone, PartialEq)] @@ -44,51 +40,50 @@ pub struct CCSShape { /// /// `l + 1`, w.r.t. the CCS/HyperNova papers. pub num_io: usize, - /// Number of matricies. + /// Number of matrices. /// /// `t` in the CCS/HyperNova papers. - pub num_matricies: usize, - /// Number of multisets + pub num_matrices: usize, + /// Number of multisets. /// /// `q` in the CCS/HyperNova papers. pub num_multisets: usize, - /// Max cardinality of the multisets + /// Max cardinality of the multisets. /// /// `d` in the CCS/HyperNova papers. pub max_cardinality: usize, + /// Set of constraint matrices. pub Ms: Vec>, - pub Ss: Vec>, - pub cs: Vec, + /// Multisets of selector indices, each paired with a constant multiplier. + pub cSs: Vec<(G::ScalarField, Vec)>, } impl CCSShape { - /// Create an object of type `CCSShape` from the specified matricies and constant data structures + /// Create an object of type `CCSShape` from the specified matrices and constant data structures pub fn new( num_constraints: usize, num_vars: usize, num_io: usize, - num_matricies: usize, + num_matrices: usize, num_multisets: usize, max_cardinality: usize, Ms: Vec>, - Ss: Vec>, - cs: Vec, + cSs: Vec<(G::ScalarField, Vec)>, ) -> Result, Error> { if num_io == 0 { return Err(Error::InvalidInputLength); } - assert_eq!(Ms.len(), num_matricies); - assert_eq!(Ss.len(), num_multisets); - assert_eq!(cs.len(), num_multisets); + assert_eq!(Ms.len(), num_matrices); + assert_eq!(cSs.len(), num_multisets); - for S in Ss.iter() { + for (_c, S) in cSs.iter() { if S.len() > max_cardinality { return Err(Error::MultisetCardinalityMismatch); } S.iter().try_for_each(|idx| { - if idx >= &num_matricies { + if idx >= &num_matrices { Err(Error::InvalidMultiset) } else { Ok(()) @@ -100,12 +95,11 @@ impl CCSShape { num_constraints, num_io, num_vars, - num_matricies, + num_matrices, num_multisets, max_cardinality, Ms, - Ss, - cs, + cSs, }) } @@ -122,22 +116,22 @@ impl CCSShape { let z = [U.X.as_slice(), W.W.as_slice()].concat(); let mut acc = vec![G::ScalarField::ZERO; self.num_constraints]; + for (c, S) in &self.cSs { + let mut circle_product = vec![*c; self.num_constraints]; + + for idx in S { + let Mz = self.Ms[*idx].multiply_vec(&z); + ark_std::cfg_iter_mut!(circle_product) + .enumerate() + .for_each(|(j, x)| *x *= Mz[j]); + } - for i in 0..self.num_multisets { - let Ms_i: Vec<&SparseMatrix> = - self.Ss[i].iter().map(|j| &self.Ms[*j]).collect(); - - let hadamard_i: Vec = Ms_i.iter().fold( - vec![G::ScalarField::ONE; self.num_constraints], - |acc, M_j| elem_mul(&acc, &M_j.multiply_vec(&z)), - ); - - let res_i: Vec = scalar_mul(&hadamard_i, &self.cs[i]); - - acc = elem_add(&acc, &res_i); + ark_std::cfg_iter_mut!(acc) + .enumerate() + .for_each(|(i, s)| *s += circle_product[i]); } - if ark_std::cfg_into_iter!(0..self.num_constraints).any(|idx| !acc[idx].is_zero()) { + if ark_std::cfg_iter!(acc).any(|s| !s.is_zero()) { return Err(Error::NotSatisfied); } @@ -156,23 +150,15 @@ impl From> for CCSShape { num_constraints: shape.num_constraints, num_io: shape.num_io, num_vars: shape.num_vars, - num_matricies: 3, + num_matrices: 3, num_multisets: 2, max_cardinality: 2, Ms: vec![shape.A, shape.B, shape.C], - Ss: vec![vec![0, 1], vec![2]], - cs: vec![G::ScalarField::ONE, G::ScalarField::ONE.neg()], + cSs: vec![(G::ScalarField::ONE, vec![0, 1]), (G::ScalarField::ONE.neg(), vec![2])], } } } -impl From> for CCSShape { - fn from(cs: ConstraintSystemRef) -> Self { - let shape = R1CSShape::from(cs); - shape.into() - } -} - /// A type that holds a witness for a given CCS instance. #[derive(Clone, Debug, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)] pub struct CCSWitness { From 54a3640a05ad267719e79834dfa35db86e450103 Mon Sep 17 00:00:00 2001 From: Samuel Judson Date: Thu, 1 Feb 2024 17:39:36 -0500 Subject: [PATCH 04/26] Fix formatting. --- nova/src/ccs/mod.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/nova/src/ccs/mod.rs b/nova/src/ccs/mod.rs index 8f835fa9a..d62d3c7d2 100644 --- a/nova/src/ccs/mod.rs +++ b/nova/src/ccs/mod.rs @@ -6,7 +6,9 @@ use ark_std::Zero; use std::ops::Neg; #[cfg(feature = "parallel")] -use rayon::iter::{ParallelIterator, IndexedParallelIterator, IntoParallelRefIterator, IntoParallelRefMutIterator}; +use rayon::iter::{ + IndexedParallelIterator, IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator, +}; use super::commitment::CommitmentScheme; @@ -154,7 +156,10 @@ impl From> for CCSShape { num_multisets: 2, max_cardinality: 2, Ms: vec![shape.A, shape.B, shape.C], - cSs: vec![(G::ScalarField::ONE, vec![0, 1]), (G::ScalarField::ONE.neg(), vec![2])], + cSs: vec![ + (G::ScalarField::ONE, vec![0, 1]), + (G::ScalarField::ONE.neg(), vec![2]), + ], } } } From 0e62e2f5466d9308008c9b013fc8e5b1a0c6dfc1 Mon Sep 17 00:00:00 2001 From: Samuel Judson Date: Thu, 1 Feb 2024 17:49:12 -0500 Subject: [PATCH 05/26] Precompute products. --- nova/src/ccs/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nova/src/ccs/mod.rs b/nova/src/ccs/mod.rs index d62d3c7d2..9ac3ceb63 100644 --- a/nova/src/ccs/mod.rs +++ b/nova/src/ccs/mod.rs @@ -116,16 +116,18 @@ impl CCSShape { assert_eq!(U.X.len(), self.num_io); let z = [U.X.as_slice(), W.W.as_slice()].concat(); + let Mzs: Vec> = ark_std::cfg_iter!(&self.Ms) + .map(|M| M.multiply_vec(&z)) + .collect(); let mut acc = vec![G::ScalarField::ZERO; self.num_constraints]; for (c, S) in &self.cSs { let mut circle_product = vec![*c; self.num_constraints]; for idx in S { - let Mz = self.Ms[*idx].multiply_vec(&z); ark_std::cfg_iter_mut!(circle_product) .enumerate() - .for_each(|(j, x)| *x *= Mz[j]); + .for_each(|(j, x)| *x *= Mzs[*idx][j]); } ark_std::cfg_iter_mut!(acc) From 5da8fec2a20aa4bcbb066b6e1748518377075144 Mon Sep 17 00:00:00 2001 From: Samuel Judson Date: Fri, 2 Feb 2024 14:02:24 -0500 Subject: [PATCH 06/26] Remove direct CCS construction. --- nova/src/ccs/mod.rs | 44 -------------------------------------------- 1 file changed, 44 deletions(-) diff --git a/nova/src/ccs/mod.rs b/nova/src/ccs/mod.rs index 9ac3ceb63..7fb502dda 100644 --- a/nova/src/ccs/mod.rs +++ b/nova/src/ccs/mod.rs @@ -61,50 +61,6 @@ pub struct CCSShape { } impl CCSShape { - /// Create an object of type `CCSShape` from the specified matrices and constant data structures - pub fn new( - num_constraints: usize, - num_vars: usize, - num_io: usize, - num_matrices: usize, - num_multisets: usize, - max_cardinality: usize, - Ms: Vec>, - cSs: Vec<(G::ScalarField, Vec)>, - ) -> Result, Error> { - if num_io == 0 { - return Err(Error::InvalidInputLength); - } - - assert_eq!(Ms.len(), num_matrices); - assert_eq!(cSs.len(), num_multisets); - - for (_c, S) in cSs.iter() { - if S.len() > max_cardinality { - return Err(Error::MultisetCardinalityMismatch); - } - - S.iter().try_for_each(|idx| { - if idx >= &num_matrices { - Err(Error::InvalidMultiset) - } else { - Ok(()) - } - })?; - } - - Ok(Self { - num_constraints, - num_io, - num_vars, - num_matrices, - num_multisets, - max_cardinality, - Ms, - cSs, - }) - } - /// Checks if the CCS instance together with the witness `W` satisfies the CCS constraints determined by `shape`. pub fn is_satisfied>( &self, From f5f2c5a5e598a9a903d496ac77e4f50caff40ac7 Mon Sep 17 00:00:00 2001 From: Samuel Judson Date: Tue, 6 Feb 2024 15:19:44 -0500 Subject: [PATCH 07/26] Add mle helpers. --- nova/src/ccs/mle.rs | 174 ++++++++++++++++++++++++++++++++++++++++++++ nova/src/ccs/mod.rs | 3 +- 2 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 nova/src/ccs/mle.rs diff --git a/nova/src/ccs/mle.rs b/nova/src/ccs/mle.rs new file mode 100644 index 000000000..0598ea018 --- /dev/null +++ b/nova/src/ccs/mle.rs @@ -0,0 +1,174 @@ +//! Helper code for multilinear extensions + +use ark_ff::{Field, PrimeField}; +use ark_poly::{DenseMultilinearExtension, MultilinearExtension, SparseMultilinearExtension}; + +use super::super::sparse::SparseMatrix; + +/// Converts a matrix into a (sparse) mle. +pub fn matrix_to_mle( + m: usize, + n: usize, + M: &SparseMatrix, +) -> SparseMultilinearExtension { + assert!(m > 0 && n > 0); + + // compute s and s' + let s1 = (m - 1).checked_ilog2().unwrap_or(0) + 1; + let s2 = (n - 1).checked_ilog2().unwrap_or(0) + 1; + + // number of columns in padded matrix + let n = n.next_power_of_two(); + + let evaluations: Vec<(usize, F)> = M.iter().map(|(i, j, value)| ((i * n + j), value)).collect(); + + SparseMultilinearExtension::from_evaluations((s1 + s2) as usize, &evaluations) +} + +/// Converts a vector into a (dense) mle. +pub fn vec_to_mle(z: &[F]) -> DenseMultilinearExtension { + let n = z.len(); + assert!(n > 0); + + let mut z = z.to_owned(); + z.resize(n.next_power_of_two(), F::zero()); + + // compute s' + let s = (n - 1).checked_ilog2().unwrap_or(0) + 1; + + DenseMultilinearExtension::from_evaluations_vec(s as usize, z) +} + +/// Folds a vector into an mle representing another, as the lower-order entries (i.e., given vector `z` and `mle` encoding a vector `y`, returns an MLE encoding `z || y`). +pub fn fold_vec_to_mle_low( + z: &[F], + mle: &DenseMultilinearExtension, +) -> DenseMultilinearExtension { + let mut n = z.len(); + assert!(n > 0); + + let mut z = z.to_owned(); + z.extend(&mle.to_evaluations()); + + n = z.len(); + z.resize(n.next_power_of_two(), F::zero()); + + // compute s' + let s = (n - 1).checked_ilog2().unwrap_or(0) + 1; + + DenseMultilinearExtension::from_evaluations_vec(s as usize, z) +} + +#[cfg(test)] +mod tests { + use super::*; + + use ark_poly::MultilinearExtension; + use ark_std::{UniformRand, Zero}; + use ark_test_curves::bls12_381::{Fr, G1Projective as G}; + + use crate::r1cs::tests::to_field_sparse; + use crate::utils::iter_bits_le; + + #[test] + fn test_matrix_to_mle() { + const NUM_ROWS: usize = 3; + const NUM_COLS: usize = 5; + + const NUM_VARS: usize = 3 + 2; // 3 bits to represent column index + 2 for rows + #[rustfmt::skip] + const M: &[&[u64]] = &[ + &[1, 2, 3, 4, 5], + &[6, 7, 8, 9, 10], + &[11, 12, 13, 14, 15], + ]; + + let sparse_m = SparseMatrix::new(&to_field_sparse::(M), NUM_ROWS, NUM_COLS); + let mle = matrix_to_mle(NUM_ROWS, NUM_COLS, &sparse_m); + + assert_eq!(mle.num_vars, NUM_VARS); + + let m = NUM_ROWS.next_power_of_two(); + let n = NUM_COLS.next_power_of_two(); + + for i in 0..m { + for j in 0..n { + let row_mask = (1 << 3) * i; // shift column bits + let _j = j | row_mask; + + let j_bytes = _j.to_le_bytes(); + let j_bits: Vec = iter_bits_le(j_bytes.as_slice()) + .map(|b| Fr::from(b)) + .take(NUM_VARS) + .collect(); + + let eval = mle.evaluate(&j_bits).unwrap(); + + let expected = if i < NUM_ROWS && j < NUM_COLS { + M[i][j].into() + } else { + Fr::zero() + }; + assert_eq!(eval, expected); + } + } + } + + #[test] + fn test_vec_to_mle() { + const LEN: usize = 100; + const NUM_VARS: usize = 7; // 7 bits to represent each index (100 < 128 = 2^7) + + let mut rng = ark_std::test_rng(); + let z: Vec = (0..LEN).map(|_| Fr::rand(&mut rng)).collect(); + let mle = vec_to_mle(&z); + + let n = LEN.next_power_of_two(); + for i in 0..n { + let i_bytes = i.to_le_bytes(); + let i_bits: Vec = iter_bits_le(i_bytes.as_slice()) + .map(|b| Fr::from(b)) + .take(NUM_VARS) + .collect(); + + let eval = mle.evaluate(&i_bits).unwrap(); + + let expected = if i < LEN { z[i].into() } else { Fr::zero() }; + assert_eq!(eval, expected); + } + } + + #[test] + fn test_fold_vec_to_mle_low() { + const VEC_LEN: usize = 30; + const MLE_LEN: usize = 60; + const TOT_LEN: usize = 90; // 60 + 30 + + const NUM_VARS: usize = 7; // 7 bits to represent each index in combination mle (30 + 60 < 128 = 2^7) + + let mut rng = ark_std::test_rng(); + let z1: Vec = (0..MLE_LEN).map(|_| Fr::rand(&mut rng)).collect(); + let z2: Vec = (0..VEC_LEN).map(|_| Fr::rand(&mut rng)).collect(); + let mle1 = vec_to_mle(&z1); + let mle2 = fold_vec_to_mle_low(&z2, &mle1); + + let n = TOT_LEN; + for i in 0..n { + let i_bytes = i.to_le_bytes(); + let i_bits: Vec = iter_bits_le(i_bytes.as_slice()) + .map(|b| Fr::from(b)) + .take(NUM_VARS) + .collect(); + + let eval = mle2.evaluate(&i_bits).unwrap(); + + if i < VEC_LEN { + assert_eq!(eval, z2[i].into()); + } else if i >= VEC_LEN && i < TOT_LEN { + assert_eq!(eval, z1[i - VEC_LEN].into()); + } else { + assert_eq!(eval, Fr::zero()); + } + } + } +} diff --git a/nova/src/ccs/mod.rs b/nova/src/ccs/mod.rs index 7fb502dda..055bb863f 100644 --- a/nova/src/ccs/mod.rs +++ b/nova/src/ccs/mod.rs @@ -12,9 +12,10 @@ use rayon::iter::{ use super::commitment::CommitmentScheme; +use super::r1cs::R1CSShape; pub use super::sparse::{MatrixRef, SparseMatrix}; -use super::r1cs::R1CSShape; +pub mod mle; #[derive(Debug, Copy, Clone, PartialEq)] pub enum Error { From 1524ba4560c4c0c04775e01f10199ea8490c09f2 Mon Sep 17 00:00:00 2001 From: Samuel Judson Date: Tue, 6 Feb 2024 17:36:11 -0500 Subject: [PATCH 08/26] Start to integrate polynomial commitments. --- nova/src/ccs/lccs.rs | 360 +++++++++++++++++++++++++++++++++++++++++++ nova/src/ccs/mod.rs | 1 + 2 files changed, 361 insertions(+) create mode 100644 nova/src/ccs/lccs.rs diff --git a/nova/src/ccs/lccs.rs b/nova/src/ccs/lccs.rs new file mode 100644 index 000000000..ea6ce87c5 --- /dev/null +++ b/nova/src/ccs/lccs.rs @@ -0,0 +1,360 @@ +use ark_ec::{AdditiveGroup, CurveGroup}; +use ark_poly::{DenseMultilinearExtension, SparseMultilinearExtension}; +use ark_poly_commit::{LabeledPolynomial, PolynomialCommitment}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_crypto_primitives::sponge::CryptographicSponge; + +use ark_std::ops::Index; + +#[cfg(feature = "parallel")] +use rayon::iter::{ + IntoParallelIterator, IntoParallelRefIterator, ParallelIterator, +}; + +use super::mle::{matrix_to_mle, vec_to_mle, fold_vec_to_mle_low}; + +pub use super::super::sparse::{MatrixRef, SparseMatrix}; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum Error { + ConstraintNumberMismatch, + InputLengthMismatch, + InvalidWitnessLength, + InvalidInputLength, + InvalidConversion, + InvalidMultiset, + MultisetCardinalityMismatch, + InvalidEvaluationPoint, + InvalidTargets, + NotSatisfied, +} + +pub struct LCCSShape { + /// `m` in the CCS/HyperNova papers. + pub num_constraints: usize, + /// Witness length. + /// + /// `m - l - 1` in the CCS/HyperNova papers. + pub num_vars: usize, + /// Length of the public input `X`. It is expected to have a leading + /// `ScalarField` element (`u`), thus this field must be non-zero. + /// + /// `l + 1`, w.r.t. the CCS/HyperNova papers. + pub num_io: usize, + /// Number of matrices. + /// + /// `t` in the CCS/HyperNova papers. + pub num_matrices: usize, + /// Number of multisets. + /// + /// `q` in the CCS/HyperNova papers. + pub num_multisets: usize, + /// Max cardinality of the multisets. + /// + /// `d` in the CCS/HyperNova papers. + pub max_cardinality: usize, + /// Set of constraint matrices. + pub Ms: Vec>, + /// Multisets of selector indices, each paired with a constant multiplier. + pub cSs: Vec<(G::ScalarField, Vec)>, +} + +impl LCCSShape { + fn validate( + num_constraints: usize, + num_vars: usize, + num_io: usize, + M: MatrixRef<'_, G::ScalarField>, + ) -> Result<(), Error> { + for (i, row) in M.iter().enumerate() { + for (_value, j) in row { + if i >= num_constraints { + return Err(Error::ConstraintNumberMismatch); + } + if *j >= num_io + num_vars { + return Err(Error::InputLengthMismatch); + } + } + } + + Ok(()) + } + + /// Create an object of type `LCCSShape` from the explicitly specified CCS matrices + pub fn new( + num_constraints: usize, + num_vars: usize, + num_io: usize, + num_matrices: usize, + num_multisets: usize, + max_cardinality: usize, + Ms: Vec>, + cSs: Vec<(G::ScalarField, Vec)>, + ) -> Result, Error> { + if num_io == 0 { + return Err(Error::InvalidInputLength); + } + + Ms.iter().try_for_each(|M| Self::validate(num_constraints, num_vars, num_io, M))?; + + assert_eq!(Ms.len(), num_matrices); + assert_eq!(cSs.len(), num_multisets); + + for (_c, S) in cSs.iter() { + if S.len() > max_cardinality { + return Err(Error::MultisetCardinalityMismatch); + } + + S.iter().try_for_each(|idx| { + if idx >= &num_matrices { + Err(Error::InvalidMultiset) + } else { + Ok(()) + } + })?; + } + + let rows = num_constraints; + let columns = num_io + num_vars; + Ok(Self { + num_constraints, + num_vars, + num_io, + num_matrices, + num_multisets, + max_cardinality, + Ms: Ms.iter().map(|M| matrix_to_mle(rows, columns, &SparseMatrix::new(M, rows, columns))).collect(), + cSs, + }) + } + + + pub fn is_satisfied, S>>( + &self, + U: &LCCSInstance, + W: &LCCSWitness, + ck: &P::CommitterKey, + ) -> Result<(), Error> { + assert_eq!(U.X.len(), self.num_io); + + let z: DenseMultilinearExtension = fold_vec_to_mle_low(&U.X, &W.W); + + let s = (self.num_constraints - 1).checked_ilog2().unwrap_or(0) + 1; + let n = (self.num_io + self.num_vars).next_power_of_two(); + + let Mzs: Vec = ark_std::cfg_iter!(&self.Ms) + .map(|M| (0..s as usize).fold(G::ScalarField::ZERO, |acc, j| acc + *M.index(n * U.r + j) * z.index(j))) + .collect(); + + if ark_std::cfg_into_iter!(0..self.num_matrices).any(|idx| Mzs[idx] != U.vs[idx]) { + return Err(Error::NotSatisfied); + } + + let lw = LabeledPolynomial::>::new("witness".to_string(), W.W, Some(1), None); + if U.commitment_W != P::commit(ck, &[lw], None).unwrap().0[0] { + return Err(Error::NotSatisfied); + } + + Ok(()) + } +} + +/// A type that holds a witness for a given LCCS instance. +#[derive(Clone, Debug, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)] +pub struct LCCSWitness { + pub W: DenseMultilinearExtension, +} + +/// A type that holds an LCCS instance. +#[derive(CanonicalSerialize, CanonicalDeserialize)] +pub struct LCCSInstance, S>> { + /// Commitment to MLE of witness. + /// + /// C in HyperNova/CCS papers. + pub commitment_W: P::Commitment, + /// X is assumed to start with a `ScalarField` field element `u`. + pub X: Vec, + /// (Random) evaluation point (row index in matrix representation of MLE) + pub r: usize, + /// Evaluation targets + pub vs: Vec, +} + +impl, S>> Clone for LCCSInstance { + fn clone(&self) -> Self { + Self { + commitment_W: self.commitment_W, + X: self.X.clone(), + r: self.r, + vs: self.vs.clone(), + } + } +} + +impl, S>> PartialEq for LCCSInstance { + fn eq(&self, other: &Self) -> bool { + self.commitment_W == other.commitment_W && self.X == other.X + } +} + +impl, S>> Eq for LCCSInstance where P::Commitment: Eq {} + +impl LCCSWitness { + /// A method to create a witness object using a vector of scalars. + pub fn new(shape: &LCCSShape, W: &[G::ScalarField]) -> Result { + if shape.num_vars != W.len() { + Err(Error::InvalidWitnessLength) + } else { + Ok(Self { W: vec_to_mle(W) }) + } + } + + pub fn zero(shape: &LCCSShape) -> Self { + Self { + W: vec_to_mle(vec![G::ScalarField::ZERO; shape.num_vars].as_slice()), + } + } + + /// Commits to the witness using the supplied generators + pub fn commit, S>>( + &self, + ck: &P::CommitterKey + ) -> P::Commitment { + let lw = LabeledPolynomial::>::new("witness".to_string(), self.W, Some(1), None); + P::commit(ck, &[lw], None).unwrap().0[0] + } +} + +impl, S>> LCCSInstance { + /// A method to create an instance object using constituent elements. + pub fn new( + shape: &LCCSShape, + commitment_W: &P::Commitment, + X: &[G::ScalarField], + r: usize, + vs: Vec, + ) -> Result { + if X.is_empty() { + return Err(Error::InvalidInputLength); + } else if shape.num_io != X.len() { + Err(Error::InvalidInputLength) + } else if shape.num_constraints <= r { + Err(Error::InvalidEvaluationPoint) + } else if shape.num_matrices != vs.len() { + Err(Error::InvalidTargets) + } else { + Ok(Self { + commitment_W: *commitment_W, + X: X.to_owned(), + r: r, + vs: vs, + }) + } + } +} + +#[cfg(test)] +mod tests { + #![allow(non_upper_case_globals)] + #![allow(clippy::needless_range_loop)] + + use super::*; + use crate::pedersen::PedersenCommitment; + + use ark_test_curves::bls12_381::{Fr, G1Projective as G}; + + use crate::r1cs::tests::{to_field_elements, to_field_sparse, A, B, C}; + + #[test] + fn zero_instance_is_satisfied() -> Result<(), Error> { + #[rustfmt::skip] + let a = { + let a: &[&[u64]] = &[ + &[1, 2, 3], + &[3, 4, 5], + &[6, 7, 8], + ]; + to_field_sparse::(a) + }; + + const NUM_CONSTRAINTS: usize = 3; + const NUM_WITNESS: usize = 1; + const NUM_PUBLIC: usize = 2; + + let pp = PedersenCommitment::::setup(NUM_WITNESS, &()); + let r1cs_shape: R1CSShape = + R1CSShape::::new(NUM_CONSTRAINTS, NUM_WITNESS, NUM_PUBLIC, &a, &a, &a).unwrap(); + + let ccs_shape = CCSShape::from(r1cs_shape); + + let X = to_field_elements::(&[0, 0]); + let W = to_field_elements::(&[0]); + let commitment_W = PedersenCommitment::::commit(&pp, &W); + + let instance = CCSInstance::>::new(&ccs_shape, &commitment_W, &X)?; + let witness = CCSWitness::::new(&ccs_shape, &W)?; + + ccs_shape.is_satisfied(&instance, &witness, &pp)?; + Ok(()) + } + + #[test] + fn is_satisfied() -> Result<(), Error> { + let (a, b, c) = { + ( + to_field_sparse::(A), + to_field_sparse::(B), + to_field_sparse::(C), + ) + }; + + const NUM_CONSTRAINTS: usize = 4; + const NUM_WITNESS: usize = 4; + const NUM_PUBLIC: usize = 2; + + let pp = PedersenCommitment::::setup(NUM_WITNESS, &()); + let r1cs_shape: R1CSShape = + R1CSShape::::new(NUM_CONSTRAINTS, NUM_WITNESS, NUM_PUBLIC, &a, &b, &c).unwrap(); + + let ccs_shape = CCSShape::from(r1cs_shape); + + let X = to_field_elements::(&[1, 35]); + let W = to_field_elements::(&[3, 9, 27, 30]); + let commitment_W = PedersenCommitment::::commit(&pp, &W); + + let instance = CCSInstance::>::new(&ccs_shape, &commitment_W, &X)?; + let witness = CCSWitness::::new(&ccs_shape, &W)?; + + ccs_shape.is_satisfied(&instance, &witness, &pp)?; + + // Change commitment. + let invalid_commitment = commitment_W.double(); + let instance = + CCSInstance::>::new(&ccs_shape, &invalid_commitment, &X)?; + assert_eq!( + ccs_shape.is_satisfied(&instance, &witness, &pp), + Err(Error::NotSatisfied) + ); + + // Provide invalid witness. + let invalid_W = to_field_elements::(&[4, 9, 27, 30]); + let commitment_invalid_W = PedersenCommitment::::commit(&pp, &W); + let instance = + CCSInstance::>::new(&ccs_shape, &commitment_invalid_W, &X)?; + let invalid_witness = CCSWitness::::new(&ccs_shape, &invalid_W)?; + assert_eq!( + ccs_shape.is_satisfied(&instance, &invalid_witness, &pp), + Err(Error::NotSatisfied) + ); + + // Provide invalid public input. + let invalid_X = to_field_elements::(&[1, 36]); + let instance = + CCSInstance::>::new(&ccs_shape, &commitment_W, &invalid_X)?; + assert_eq!( + ccs_shape.is_satisfied(&instance, &witness, &pp), + Err(Error::NotSatisfied) + ); + Ok(()) + } +} diff --git a/nova/src/ccs/mod.rs b/nova/src/ccs/mod.rs index 055bb863f..6ac3465ba 100644 --- a/nova/src/ccs/mod.rs +++ b/nova/src/ccs/mod.rs @@ -15,6 +15,7 @@ use super::commitment::CommitmentScheme; use super::r1cs::R1CSShape; pub use super::sparse::{MatrixRef, SparseMatrix}; +pub mod lccs; pub mod mle; #[derive(Debug, Copy, Clone, PartialEq)] From c4dbc6549b198b2ad3e4639b1ee444c6a2b30d96 Mon Sep 17 00:00:00 2001 From: Samuel Judson Date: Wed, 7 Feb 2024 12:29:12 -0500 Subject: [PATCH 09/26] Shading closer to polynomial commitments. --- nova/src/ccs/lccs.rs | 102 ++++++++++++++++++++++++++++--------------- nova/src/ccs/mle.rs | 22 ++++++++-- nova/src/utils.rs | 70 ++++++++++++++++++++++++++++- 3 files changed, 153 insertions(+), 41 deletions(-) diff --git a/nova/src/ccs/lccs.rs b/nova/src/ccs/lccs.rs index ea6ce87c5..0a52951ec 100644 --- a/nova/src/ccs/lccs.rs +++ b/nova/src/ccs/lccs.rs @@ -1,5 +1,5 @@ use ark_ec::{AdditiveGroup, CurveGroup}; -use ark_poly::{DenseMultilinearExtension, SparseMultilinearExtension}; +use ark_poly::{Polynomial, DenseMultilinearExtension, SparseMultilinearExtension}; use ark_poly_commit::{LabeledPolynomial, PolynomialCommitment}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_crypto_primitives::sponge::CryptographicSponge; @@ -12,7 +12,7 @@ use rayon::iter::{ }; use super::mle::{matrix_to_mle, vec_to_mle, fold_vec_to_mle_low}; - +use super::super::utils::index_to_le_field_encoding; pub use super::super::sparse::{MatrixRef, SparseMatrix}; #[derive(Debug, Copy, Clone, PartialEq)] @@ -128,8 +128,7 @@ impl LCCSShape { }) } - - pub fn is_satisfied, S>>( + pub fn is_satisfied, S> + PartialEq>( &self, U: &LCCSInstance, W: &LCCSWitness, @@ -139,19 +138,20 @@ impl LCCSShape { let z: DenseMultilinearExtension = fold_vec_to_mle_low(&U.X, &W.W); - let s = (self.num_constraints - 1).checked_ilog2().unwrap_or(0) + 1; - let n = (self.num_io + self.num_vars).next_power_of_two(); + let s = (self.num_io + self.num_vars - 1).checked_ilog2().unwrap_or(0) + 1; // s' in papers + + let rys: Vec> = (0..s as usize).map(|y| [U.rs.as_slice(), index_to_le_field_encoding(y as u32, Some(s)).as_slice()].concat()).collect(); let Mzs: Vec = ark_std::cfg_iter!(&self.Ms) - .map(|M| (0..s as usize).fold(G::ScalarField::ZERO, |acc, j| acc + *M.index(n * U.r + j) * z.index(j))) + .map(|M| (0..s as usize).map(|y| M.evaluate(&rys[y]) * z.index(y)).sum()) .collect(); if ark_std::cfg_into_iter!(0..self.num_matrices).any(|idx| Mzs[idx] != U.vs[idx]) { return Err(Error::NotSatisfied); } - let lw = LabeledPolynomial::>::new("witness".to_string(), W.W, Some(1), None); - if U.commitment_W != P::commit(ck, &[lw], None).unwrap().0[0] { + let lw = LabeledPolynomial::>::new("witness".to_string(), W.W, Some(W.W.num_vars), None); + if U.commitment_W != *P::commit(ck, &[lw], None).unwrap().0[0].commitment() { return Err(Error::NotSatisfied); } @@ -167,37 +167,37 @@ pub struct LCCSWitness { /// A type that holds an LCCS instance. #[derive(CanonicalSerialize, CanonicalDeserialize)] -pub struct LCCSInstance, S>> { +pub struct LCCSInstance, S> + PartialEq> { /// Commitment to MLE of witness. /// /// C in HyperNova/CCS papers. pub commitment_W: P::Commitment, /// X is assumed to start with a `ScalarField` field element `u`. pub X: Vec, - /// (Random) evaluation point (row index in matrix representation of MLE) - pub r: usize, + /// (Random) evaluation point + pub rs: Vec, /// Evaluation targets pub vs: Vec, } -impl, S>> Clone for LCCSInstance { +impl, S> + PartialEq> Clone for LCCSInstance { fn clone(&self) -> Self { Self { commitment_W: self.commitment_W, X: self.X.clone(), - r: self.r, + rs: self.rs.clone(), vs: self.vs.clone(), } } } -impl, S>> PartialEq for LCCSInstance { +impl, S> + PartialEq> PartialEq for LCCSInstance { fn eq(&self, other: &Self) -> bool { self.commitment_W == other.commitment_W && self.X == other.X } } -impl, S>> Eq for LCCSInstance where P::Commitment: Eq {} +impl, S> + PartialEq> Eq for LCCSInstance where P::Commitment: Eq {} impl LCCSWitness { /// A method to create a witness object using a vector of scalars. @@ -215,30 +215,32 @@ impl LCCSWitness { } } - /// Commits to the witness using the supplied generators - pub fn commit, S>>( + /// Commits to the witness using the supplied key + pub fn commit, S> + PartialEq>( &self, ck: &P::CommitterKey ) -> P::Commitment { - let lw = LabeledPolynomial::>::new("witness".to_string(), self.W, Some(1), None); - P::commit(ck, &[lw], None).unwrap().0[0] + let lw = LabeledPolynomial::>::new("witness".to_string(), self.W, Some(self.W.num_vars), None); + let wc = P::commit(ck, &[lw], None).unwrap().0[0].commitment(); + + *wc } } -impl, S>> LCCSInstance { +impl, S> + PartialEq> LCCSInstance { /// A method to create an instance object using constituent elements. pub fn new( shape: &LCCSShape, commitment_W: &P::Commitment, X: &[G::ScalarField], - r: usize, + rs: Vec, vs: Vec, ) -> Result { if X.is_empty() { return Err(Error::InvalidInputLength); } else if shape.num_io != X.len() { Err(Error::InvalidInputLength) - } else if shape.num_constraints <= r { + } else if ((shape.num_constraints - 1).checked_ilog2().unwrap_or(0) + 1) != rs.len() as u32 { Err(Error::InvalidEvaluationPoint) } else if shape.num_matrices != vs.len() { Err(Error::InvalidTargets) @@ -246,7 +248,7 @@ impl as ark_ec::PrimeGroup>::ScalarField; + + type DMLE = DenseMultilinearExtension; + type MPST = MarlinPST13::>; use crate::r1cs::tests::{to_field_elements, to_field_sparse, A, B, C}; #[test] fn zero_instance_is_satisfied() -> Result<(), Error> { + #[rustfmt::skip] let a = { let a: &[&[u64]] = &[ @@ -281,23 +294,39 @@ mod tests { const NUM_WITNESS: usize = 1; const NUM_PUBLIC: usize = 2; - let pp = PedersenCommitment::::setup(NUM_WITNESS, &()); - let r1cs_shape: R1CSShape = - R1CSShape::::new(NUM_CONSTRAINTS, NUM_WITNESS, NUM_PUBLIC, &a, &a, &a).unwrap(); - - let ccs_shape = CCSShape::from(r1cs_shape); + let lccs_shape = LCCSShape::::new(NUM_CONSTRAINTS, + NUM_WITNESS, + NUM_PUBLIC, + 3, + 2, + 2, + vec![&a, &a, &a], + vec![ + (F::ONE, vec![0, 1]), + (F::ONE.neg(), vec![2]), + ], + )?; let X = to_field_elements::(&[0, 0]); let W = to_field_elements::(&[0]); - let commitment_W = PedersenCommitment::::commit(&pp, &W); + let witness = LCCSWitness::::new(&lccs_shape, &W)?; - let instance = CCSInstance::>::new(&ccs_shape, &commitment_W, &X)?; - let witness = CCSWitness::::new(&ccs_shape, &W)?; + let up = MPST::setup(witness.W.num_vars, false, None); + let (ck, _vk) = MPST::trim(&up, witness.W.num_vars, 0, witness.W.num_vars); - ccs_shape.is_satisfied(&instance, &witness, &pp)?; + let commitment_W = witness.commit::, MPST>(&ck); + + let s = (NUM_CONSTRAINTS - 1).checked_ilog2().unwrap_or(0) + 1; + + let mut rng = rand::thread_rng(); + let rs: Vec = (0..s).map(|_| F::random(rng)).collect(); + + let instance = LCCSInstance::, MPST>::new(&lccs_shape, &commitment_W, &X, rs, )?; + + lccs_shape.is_satisfied(&instance, &witness, &ck)?; Ok(()) } - +/* #[test] fn is_satisfied() -> Result<(), Error> { let (a, b, c) = { @@ -357,4 +386,5 @@ mod tests { ); Ok(()) } +*/ } diff --git a/nova/src/ccs/mle.rs b/nova/src/ccs/mle.rs index 0598ea018..d940f2096 100644 --- a/nova/src/ccs/mle.rs +++ b/nova/src/ccs/mle.rs @@ -5,6 +5,20 @@ use ark_poly::{DenseMultilinearExtension, MultilinearExtension, SparseMultilinea use super::super::sparse::SparseMatrix; +impl DenseMVPolynomial for DenseMultilinearExtension { + type Term: multivariate::Term; + + fn from_coefficients_vec(num_vars: usize, terms: Vec<(F, Self::Term)>) -> Self; + + fn terms(&self) -> &[(F, Self::Term)]; + + fn num_vars(&self) -> usize { + self.num_vars + }; + + fn rand(d: usize, num_vars: usize, rng: &mut R) -> Self; +} + /// Converts a matrix into a (sparse) mle. pub fn matrix_to_mle( m: usize, @@ -63,7 +77,7 @@ pub fn fold_vec_to_mle_low( mod tests { use super::*; - use ark_poly::MultilinearExtension; + use ark_poly::Polynomial; use ark_std::{UniformRand, Zero}; use ark_test_curves::bls12_381::{Fr, G1Projective as G}; @@ -102,7 +116,7 @@ mod tests { .take(NUM_VARS) .collect(); - let eval = mle.evaluate(&j_bits).unwrap(); + let eval = mle.evaluate(&j_bits); let expected = if i < NUM_ROWS && j < NUM_COLS { M[i][j].into() @@ -131,7 +145,7 @@ mod tests { .take(NUM_VARS) .collect(); - let eval = mle.evaluate(&i_bits).unwrap(); + let eval = mle.evaluate(&i_bits); let expected = if i < LEN { z[i].into() } else { Fr::zero() }; assert_eq!(eval, expected); @@ -160,7 +174,7 @@ mod tests { .take(NUM_VARS) .collect(); - let eval = mle2.evaluate(&i_bits).unwrap(); + let eval = mle2.evaluate(&i_bits); if i < VEC_LEN { assert_eq!(eval, z2[i].into()); diff --git a/nova/src/utils.rs b/nova/src/utils.rs index 7d402c213..c0e69556b 100644 --- a/nova/src/utils.rs +++ b/nova/src/utils.rs @@ -35,9 +35,26 @@ pub fn iter_bits_le(bytes: &[u8]) -> impl Iterator + '_ { .flat_map(|byte| (0..8).map(move |bit| ((1 << bit) & byte) != 0)) } +/// Returns field encoded bits in little-endian order. +pub fn index_to_le_field_encoding(idx: u32, trim: Option) -> Vec { + let mut ot = trim; + if let None = ot { + ot = Some(32); + } + let t = ot.unwrap() as usize; + assert!(t <= 32); + + iter_bits_le(&idx.to_le_bytes()) + .map(|byte| if byte { F::ONE } else { F::ZERO }) + .collect::>()[0..t] + .to_vec() +} + #[cfg(test)] mod tests { - use ark_ff::{BigInteger, PrimeField}; + use ark_ec::AdditiveGroup; + use ark_ff::{BigInteger, Field, PrimeField}; + use ark_pallas::Fr; use ark_std::UniformRand; type BigInt = ::BigInt; @@ -55,4 +72,55 @@ mod tests { assert_eq!(BigInt::from_bits_le(&bits), big_int); } } + + #[test] + fn index_le_field_encoding() { + const X: u32 = 13; // 1101 + + assert_eq!( + super::index_to_le_field_encoding::(X, Some(4)), + [Fr::ONE, Fr::ZERO, Fr::ONE, Fr::ONE] + ); + assert_eq!( + super::index_to_le_field_encoding::(X, Some(5)), + [Fr::ONE, Fr::ZERO, Fr::ONE, Fr::ONE, Fr::ZERO] + ); + assert_eq!( + super::index_to_le_field_encoding::(X, None), + [ + Fr::ONE, + Fr::ZERO, + Fr::ONE, + Fr::ONE, + Fr::ZERO, + Fr::ZERO, + Fr::ZERO, + Fr::ZERO, + Fr::ZERO, + Fr::ZERO, + Fr::ZERO, + Fr::ZERO, + Fr::ZERO, + Fr::ZERO, + Fr::ZERO, + Fr::ZERO, + Fr::ZERO, + Fr::ZERO, + Fr::ZERO, + Fr::ZERO, + Fr::ZERO, + Fr::ZERO, + Fr::ZERO, + Fr::ZERO, + Fr::ZERO, + Fr::ZERO, + Fr::ZERO, + Fr::ZERO, + Fr::ZERO, + Fr::ZERO, + Fr::ZERO, + Fr::ZERO, + ] + ); + } } From c2d2104a2eed1a8b6f9311cb0735fcd5865ebc22 Mon Sep 17 00:00:00 2001 From: Samuel Judson Date: Wed, 7 Feb 2024 15:26:05 -0500 Subject: [PATCH 10/26] Initial stab at relating various polynomial types and traits. --- nova/src/ccs/lccs.rs | 24 +++++++++++++++--------- nova/src/ccs/mle.rs | 20 ++++++++++---------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/nova/src/ccs/lccs.rs b/nova/src/ccs/lccs.rs index 0a52951ec..620673423 100644 --- a/nova/src/ccs/lccs.rs +++ b/nova/src/ccs/lccs.rs @@ -1,5 +1,5 @@ use ark_ec::{AdditiveGroup, CurveGroup}; -use ark_poly::{Polynomial, DenseMultilinearExtension, SparseMultilinearExtension}; +use ark_poly::{polynomial::multivariate::{SparsePolynomial, SparseTerm}, Polynomial, DenseMultilinearExtension, SparseMultilinearExtension}; use ark_poly_commit::{LabeledPolynomial, PolynomialCommitment}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_crypto_primitives::sponge::CryptographicSponge; @@ -11,7 +11,7 @@ use rayon::iter::{ IntoParallelIterator, IntoParallelRefIterator, ParallelIterator, }; -use super::mle::{matrix_to_mle, vec_to_mle, fold_vec_to_mle_low}; +use super::mle::{mle_to_mvp, matrix_to_mle, vec_to_mle, fold_vec_to_mle_low}; use super::super::utils::index_to_le_field_encoding; pub use super::super::sparse::{MatrixRef, SparseMatrix}; @@ -150,8 +150,11 @@ impl LCCSShape { return Err(Error::NotSatisfied); } - let lw = LabeledPolynomial::>::new("witness".to_string(), W.W, Some(W.W.num_vars), None); - if U.commitment_W != *P::commit(ck, &[lw], None).unwrap().0[0].commitment() { + let mvp_W = mle_to_mvp(&W.W); + + // confusingly, the DenseMVPolynomial trait is only implemented by SparsePolynomial + let lab_W = LabeledPolynomial::>::new("witness".to_string(), mvp_W, Some(W.W.num_vars), None); + if U.commitment_W != *P::commit(ck, &[lab_W], None).unwrap().0[0].commitment() { return Err(Error::NotSatisfied); } @@ -220,14 +223,17 @@ impl LCCSWitness { &self, ck: &P::CommitterKey ) -> P::Commitment { - let lw = LabeledPolynomial::>::new("witness".to_string(), self.W, Some(self.W.num_vars), None); - let wc = P::commit(ck, &[lw], None).unwrap().0[0].commitment(); + let mvp_W: SparsePolynomial = mle_to_mvp(&self.W); + + // confusingly, the DenseMVPolynomial trait is only implemented by SparsePolynomial + let lab_W = LabeledPolynomial::>::new("witness".to_string(), mvp_W, Some(self.W.num_vars), None); + let wc = P::commit(ck, &[lab_W], None).unwrap().0[0].commitment(); *wc } } -impl, S> + PartialEq> LCCSInstance { +impl, S> + PartialEq> LCCSInstance { /// A method to create an instance object using constituent elements. pub fn new( shape: &LCCSShape, @@ -272,8 +278,8 @@ mod tests { type F = as ark_ec::PrimeGroup>::ScalarField; - type DMLE = DenseMultilinearExtension; - type MPST = MarlinPST13::>; + // confusingly, the DenseMVPolynomial trait is only implemented by SparsePolynomial + type MPST = MarlinPST13::, PoseidonSponge>; use crate::r1cs::tests::{to_field_elements, to_field_sparse, A, B, C}; diff --git a/nova/src/ccs/mle.rs b/nova/src/ccs/mle.rs index d940f2096..aec749854 100644 --- a/nova/src/ccs/mle.rs +++ b/nova/src/ccs/mle.rs @@ -1,22 +1,22 @@ //! Helper code for multilinear extensions use ark_ff::{Field, PrimeField}; -use ark_poly::{DenseMultilinearExtension, MultilinearExtension, SparseMultilinearExtension}; +use ark_poly::{ + polynomial::multivariate::{SparsePolynomial, SparseTerm, Term}, + DenseMVPolynomial, DenseMultilinearExtension, MultilinearExtension, SparseMultilinearExtension +}; use super::super::sparse::SparseMatrix; -impl DenseMVPolynomial for DenseMultilinearExtension { - type Term: multivariate::Term; +pub fn mle_to_mvp( + mle: &DenseMultilinearExtension +) -> SparsePolynomial { // confusingly, the DenseMVPolynomial trait is only implemented by SparsePolynomial - fn from_coefficients_vec(num_vars: usize, terms: Vec<(F, Self::Term)>) -> Self; + let points = 1 << mle.num_vars; - fn terms(&self) -> &[(F, Self::Term)]; + (0..points).iter().map(|pt| ).collect(); - fn num_vars(&self) -> usize { - self.num_vars - }; - - fn rand(d: usize, num_vars: usize, rng: &mut R) -> Self; + SparsePolynomial::::from_coefficients_vec(mle.num_vars, coeffs) } /// Converts a matrix into a (sparse) mle. From 633db56c71f1e690da6d20d926f2c23a0d6dd088 Mon Sep 17 00:00:00 2001 From: Samuel Judson Date: Thu, 8 Feb 2024 11:21:05 -0500 Subject: [PATCH 11/26] Finish utility functions. --- nova/src/ccs/lccs.rs | 324 +++++++++++++++++++++++++++---------------- nova/src/ccs/mle.rs | 84 +++++++++-- nova/src/utils.rs | 14 +- 3 files changed, 286 insertions(+), 136 deletions(-) diff --git a/nova/src/ccs/lccs.rs b/nova/src/ccs/lccs.rs index 620673423..d79d18a76 100644 --- a/nova/src/ccs/lccs.rs +++ b/nova/src/ccs/lccs.rs @@ -1,19 +1,20 @@ +use ark_crypto_primitives::sponge::CryptographicSponge; use ark_ec::{AdditiveGroup, CurveGroup}; -use ark_poly::{polynomial::multivariate::{SparsePolynomial, SparseTerm}, Polynomial, DenseMultilinearExtension, SparseMultilinearExtension}; +use ark_poly::{ + DenseMVPolynomial, DenseMultilinearExtension, MultilinearExtension, Polynomial, + SparseMultilinearExtension, +}; use ark_poly_commit::{LabeledPolynomial, PolynomialCommitment}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_crypto_primitives::sponge::CryptographicSponge; use ark_std::ops::Index; #[cfg(feature = "parallel")] -use rayon::iter::{ - IntoParallelIterator, IntoParallelRefIterator, ParallelIterator, -}; +use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator}; -use super::mle::{mle_to_mvp, matrix_to_mle, vec_to_mle, fold_vec_to_mle_low}; -use super::super::utils::index_to_le_field_encoding; pub use super::super::sparse::{MatrixRef, SparseMatrix}; +use super::super::utils::index_to_le_field_encoding; +use super::mle::{fold_vec_to_mle_low, matrix_to_mle, mle_to_mvp, vec_to_mle}; #[derive(Debug, Copy, Clone, PartialEq)] pub enum Error { @@ -95,7 +96,8 @@ impl LCCSShape { return Err(Error::InvalidInputLength); } - Ms.iter().try_for_each(|M| Self::validate(num_constraints, num_vars, num_io, M))?; + Ms.iter() + .try_for_each(|M| Self::validate(num_constraints, num_vars, num_io, M))?; assert_eq!(Ms.len(), num_matrices); assert_eq!(cSs.len(), num_multisets); @@ -123,37 +125,65 @@ impl LCCSShape { num_matrices, num_multisets, max_cardinality, - Ms: Ms.iter().map(|M| matrix_to_mle(rows, columns, &SparseMatrix::new(M, rows, columns))).collect(), + Ms: Ms + .iter() + .map(|M| matrix_to_mle(rows, columns, &SparseMatrix::new(M, rows, columns))) + .collect(), cSs, }) } - pub fn is_satisfied, S> + PartialEq>( + pub fn is_satisfied< + M: DenseMVPolynomial, + S: CryptographicSponge, + P: PolynomialCommitment, + >( &self, - U: &LCCSInstance, + U: &LCCSInstance, W: &LCCSWitness, ck: &P::CommitterKey, - ) -> Result<(), Error> { + ) -> Result<(), Error> + where + P::Commitment: PartialEq, + { assert_eq!(U.X.len(), self.num_io); let z: DenseMultilinearExtension = fold_vec_to_mle_low(&U.X, &W.W); - let s = (self.num_io + self.num_vars - 1).checked_ilog2().unwrap_or(0) + 1; // s' in papers + let Mrs: Vec> = ark_std::cfg_iter!(&self.Ms) + .map(|M| M.fix_variables(U.rs.as_slice())) + .collect(); + + let s = (self.num_io + self.num_vars - 1) + .checked_ilog2() + .unwrap_or(0) + + 1; // s' in papers - let rys: Vec> = (0..s as usize).map(|y| [U.rs.as_slice(), index_to_le_field_encoding(y as u32, Some(s)).as_slice()].concat()).collect(); + let ys: Vec> = ark_std::cfg_into_iter!(0..s as usize) + .map(|y| index_to_le_field_encoding(y as u32, Some(s))) + .collect(); - let Mzs: Vec = ark_std::cfg_iter!(&self.Ms) - .map(|M| (0..s as usize).map(|y| M.evaluate(&rys[y]) * z.index(y)).sum()) + let Mzs: Vec = ark_std::cfg_iter!(Mrs) + .map(|M| { + (0..s as usize) + .map(|y| M.evaluate(&ys[y]) * z.index(y)) + .sum() + }) .collect(); if ark_std::cfg_into_iter!(0..self.num_matrices).any(|idx| Mzs[idx] != U.vs[idx]) { return Err(Error::NotSatisfied); } - let mvp_W = mle_to_mvp(&W.W); + let mvp_W: M = mle_to_mvp::(&W.W); + + let lab_W = LabeledPolynomial::::new( + "witness".to_string(), + mvp_W, + Some(W.W.num_vars), + None, + ); - // confusingly, the DenseMVPolynomial trait is only implemented by SparsePolynomial - let lab_W = LabeledPolynomial::>::new("witness".to_string(), mvp_W, Some(W.W.num_vars), None); if U.commitment_W != *P::commit(ck, &[lab_W], None).unwrap().0[0].commitment() { return Err(Error::NotSatisfied); } @@ -170,7 +200,12 @@ pub struct LCCSWitness { /// A type that holds an LCCS instance. #[derive(CanonicalSerialize, CanonicalDeserialize)] -pub struct LCCSInstance, S> + PartialEq> { +pub struct LCCSInstance< + G: CurveGroup, + M: DenseMVPolynomial, + S: CryptographicSponge, + P: PolynomialCommitment, +> { /// Commitment to MLE of witness. /// /// C in HyperNova/CCS papers. @@ -183,10 +218,16 @@ pub struct LCCSInstance, } -impl, S> + PartialEq> Clone for LCCSInstance { +impl< + G: CurveGroup, + M: DenseMVPolynomial, + S: CryptographicSponge, + P: PolynomialCommitment, + > Clone for LCCSInstance +{ fn clone(&self) -> Self { Self { - commitment_W: self.commitment_W, + commitment_W: self.commitment_W.clone(), X: self.X.clone(), rs: self.rs.clone(), vs: self.vs.clone(), @@ -194,13 +235,33 @@ impl, S> + PartialEq> PartialEq for LCCSInstance { - fn eq(&self, other: &Self) -> bool { +impl< + G: CurveGroup, + M: DenseMVPolynomial, + S: CryptographicSponge, + P: PolynomialCommitment, + > PartialEq for LCCSInstance +where + P::Commitment: PartialEq, +{ + fn eq(&self, other: &Self) -> bool + where + P::Commitment: PartialEq, + { self.commitment_W == other.commitment_W && self.X == other.X } } -impl, S> + PartialEq> Eq for LCCSInstance where P::Commitment: Eq {} +impl< + G: CurveGroup, + M: DenseMVPolynomial, + S: CryptographicSponge, + P: PolynomialCommitment, + > Eq for LCCSInstance +where + P::Commitment: Eq, +{ +} impl LCCSWitness { /// A method to create a witness object using a vector of scalars. @@ -219,21 +280,35 @@ impl LCCSWitness { } /// Commits to the witness using the supplied key - pub fn commit, S> + PartialEq>( + pub fn commit< + M: DenseMVPolynomial, + S: CryptographicSponge, + P: PolynomialCommitment, + >( &self, - ck: &P::CommitterKey + ck: &P::CommitterKey, ) -> P::Commitment { - let mvp_W: SparsePolynomial = mle_to_mvp(&self.W); + let mvp_W: M = mle_to_mvp::(&self.W); - // confusingly, the DenseMVPolynomial trait is only implemented by SparsePolynomial - let lab_W = LabeledPolynomial::>::new("witness".to_string(), mvp_W, Some(self.W.num_vars), None); - let wc = P::commit(ck, &[lab_W], None).unwrap().0[0].commitment(); + let lab_W = LabeledPolynomial::::new( + "witness".to_string(), + mvp_W, + Some(self.W.num_vars), + None, + ); - *wc + let wc = P::commit(ck, &[lab_W], None).unwrap(); + wc.0[0].commitment().clone() } } -impl, S> + PartialEq> LCCSInstance { +impl< + G: CurveGroup, + M: DenseMVPolynomial, + S: CryptographicSponge, + P: PolynomialCommitment, + > LCCSInstance +{ /// A method to create an instance object using constituent elements. pub fn new( shape: &LCCSShape, @@ -246,16 +321,17 @@ impl as ark_ec::PrimeGroup>::ScalarField; - // confusingly, the DenseMVPolynomial trait is only implemented by SparsePolynomial - type MPST = MarlinPST13::, PoseidonSponge>; + type M = SparsePolynomial; // confusingly, the DenseMVPolynomial trait is only implemented by SparsePolynomial + type S = PoseidonSponge; + type P = MarlinPST13; use crate::r1cs::tests::{to_field_elements, to_field_sparse, A, B, C}; - +/* #[test] fn zero_instance_is_satisfied() -> Result<(), Error> { - #[rustfmt::skip] let a = { let a: &[&[u64]] = &[ @@ -296,101 +374,101 @@ mod tests { to_field_sparse::(a) }; + let mut rng = rand::thread_rng(); + const NUM_CONSTRAINTS: usize = 3; const NUM_WITNESS: usize = 1; const NUM_PUBLIC: usize = 2; - let lccs_shape = LCCSShape::::new(NUM_CONSTRAINTS, - NUM_WITNESS, - NUM_PUBLIC, - 3, - 2, - 2, - vec![&a, &a, &a], - vec![ - (F::ONE, vec![0, 1]), - (F::ONE.neg(), vec![2]), - ], + let lccs_shape = LCCSShape::::new( + NUM_CONSTRAINTS, + NUM_WITNESS, + NUM_PUBLIC, + 3, + 2, + 2, + vec![&a, &a, &a], + vec![(F::ONE, vec![0, 1]), (F::ONE.neg(), vec![2])], )?; let X = to_field_elements::(&[0, 0]); let W = to_field_elements::(&[0]); let witness = LCCSWitness::::new(&lccs_shape, &W)?; - let up = MPST::setup(witness.W.num_vars, false, None); - let (ck, _vk) = MPST::trim(&up, witness.W.num_vars, 0, witness.W.num_vars); + let up = P::setup(witness.W.num_vars, Some(witness.W.num_vars), &mut rng).unwrap(); + let (ck, _vk) = P::trim(&up, witness.W.num_vars, 0, None).unwrap(); - let commitment_W = witness.commit::, MPST>(&ck); + let commitment_W = witness.commit::(&ck); let s = (NUM_CONSTRAINTS - 1).checked_ilog2().unwrap_or(0) + 1; - let mut rng = rand::thread_rng(); let rs: Vec = (0..s).map(|_| F::random(rng)).collect(); - let instance = LCCSInstance::, MPST>::new(&lccs_shape, &commitment_W, &X, rs, )?; - - lccs_shape.is_satisfied(&instance, &witness, &ck)?; - Ok(()) - } -/* - #[test] - fn is_satisfied() -> Result<(), Error> { - let (a, b, c) = { - ( - to_field_sparse::(A), - to_field_sparse::(B), - to_field_sparse::(C), - ) - }; - - const NUM_CONSTRAINTS: usize = 4; - const NUM_WITNESS: usize = 4; - const NUM_PUBLIC: usize = 2; - - let pp = PedersenCommitment::::setup(NUM_WITNESS, &()); - let r1cs_shape: R1CSShape = - R1CSShape::::new(NUM_CONSTRAINTS, NUM_WITNESS, NUM_PUBLIC, &a, &b, &c).unwrap(); + let instance = LCCSInstance::::new(&lccs_shape, &commitment_W, &X, rs)?; - let ccs_shape = CCSShape::from(r1cs_shape); - - let X = to_field_elements::(&[1, 35]); - let W = to_field_elements::(&[3, 9, 27, 30]); - let commitment_W = PedersenCommitment::::commit(&pp, &W); - - let instance = CCSInstance::>::new(&ccs_shape, &commitment_W, &X)?; - let witness = CCSWitness::::new(&ccs_shape, &W)?; - - ccs_shape.is_satisfied(&instance, &witness, &pp)?; - - // Change commitment. - let invalid_commitment = commitment_W.double(); - let instance = - CCSInstance::>::new(&ccs_shape, &invalid_commitment, &X)?; - assert_eq!( - ccs_shape.is_satisfied(&instance, &witness, &pp), - Err(Error::NotSatisfied) - ); - - // Provide invalid witness. - let invalid_W = to_field_elements::(&[4, 9, 27, 30]); - let commitment_invalid_W = PedersenCommitment::::commit(&pp, &W); - let instance = - CCSInstance::>::new(&ccs_shape, &commitment_invalid_W, &X)?; - let invalid_witness = CCSWitness::::new(&ccs_shape, &invalid_W)?; - assert_eq!( - ccs_shape.is_satisfied(&instance, &invalid_witness, &pp), - Err(Error::NotSatisfied) - ); - - // Provide invalid public input. - let invalid_X = to_field_elements::(&[1, 36]); - let instance = - CCSInstance::>::new(&ccs_shape, &commitment_W, &invalid_X)?; - assert_eq!( - ccs_shape.is_satisfied(&instance, &witness, &pp), - Err(Error::NotSatisfied) - ); + lccs_shape.is_satisfied::(&instance, &witness, &ck)?; Ok(()) - } -*/ +} + */ + /* + #[test] + fn is_satisfied() -> Result<(), Error> { + let (a, b, c) = { + ( + to_field_sparse::(A), + to_field_sparse::(B), + to_field_sparse::(C), + ) + }; + + const NUM_CONSTRAINTS: usize = 4; + const NUM_WITNESS: usize = 4; + const NUM_PUBLIC: usize = 2; + + let pp = PedersenCommitment::::setup(NUM_WITNESS, &()); + let r1cs_shape: R1CSShape = + R1CSShape::::new(NUM_CONSTRAINTS, NUM_WITNESS, NUM_PUBLIC, &a, &b, &c).unwrap(); + + let ccs_shape = CCSShape::from(r1cs_shape); + + let X = to_field_elements::(&[1, 35]); + let W = to_field_elements::(&[3, 9, 27, 30]); + let commitment_W = PedersenCommitment::::commit(&pp, &W); + + let instance = CCSInstance::>::new(&ccs_shape, &commitment_W, &X)?; + let witness = CCSWitness::::new(&ccs_shape, &W)?; + + ccs_shape.is_satisfied(&instance, &witness, &pp)?; + + // Change commitment. + let invalid_commitment = commitment_W.double(); + let instance = + CCSInstance::>::new(&ccs_shape, &invalid_commitment, &X)?; + assert_eq!( + ccs_shape.is_satisfied(&instance, &witness, &pp), + Err(Error::NotSatisfied) + ); + + // Provide invalid witness. + let invalid_W = to_field_elements::(&[4, 9, 27, 30]); + let commitment_invalid_W = PedersenCommitment::::commit(&pp, &W); + let instance = + CCSInstance::>::new(&ccs_shape, &commitment_invalid_W, &X)?; + let invalid_witness = CCSWitness::::new(&ccs_shape, &invalid_W)?; + assert_eq!( + ccs_shape.is_satisfied(&instance, &invalid_witness, &pp), + Err(Error::NotSatisfied) + ); + + // Provide invalid public input. + let invalid_X = to_field_elements::(&[1, 36]); + let instance = + CCSInstance::>::new(&ccs_shape, &commitment_W, &invalid_X)?; + assert_eq!( + ccs_shape.is_satisfied(&instance, &witness, &pp), + Err(Error::NotSatisfied) + ); + Ok(()) + } + */ } diff --git a/nova/src/ccs/mle.rs b/nova/src/ccs/mle.rs index aec749854..1f7204941 100644 --- a/nova/src/ccs/mle.rs +++ b/nova/src/ccs/mle.rs @@ -1,22 +1,56 @@ //! Helper code for multilinear extensions use ark_ff::{Field, PrimeField}; -use ark_poly::{ - polynomial::multivariate::{SparsePolynomial, SparseTerm, Term}, - DenseMVPolynomial, DenseMultilinearExtension, MultilinearExtension, SparseMultilinearExtension -}; +use ark_poly::{multivariate::Term, DenseMVPolynomial, DenseMultilinearExtension, MultilinearExtension, SparseMultilinearExtension}; use super::super::sparse::SparseMatrix; +use super::super::utils::iter_bits_le; -pub fn mle_to_mvp( - mle: &DenseMultilinearExtension -) -> SparsePolynomial { // confusingly, the DenseMVPolynomial trait is only implemented by SparsePolynomial +/// Utility function for mle -> mvp conversion. +fn ext(pts: &[F]) -> Vec { + let n = pts.len(); - let points = 1 << mle.num_vars; + // https://crypto.stackexchange.com/a/84416 + if n == 1 { + return pts.to_vec(); + } + + let h = n / 2; + let l = ext(&pts[0..h]); + let r = ext(&pts[h..n]); + + [ + l.clone(), + l.iter() + .zip(r.iter()) + .map(|(vl, vr)| *vr - vl) + .collect::>(), + ] + .concat() +} + +/// Converts an mle into a generic multivariate polynomial. +pub fn mle_to_mvp>(mle: &DenseMultilinearExtension) -> M { + let evals = mle.to_evaluations(); + let coeffs = ext(evals.as_slice()); + + let n = 1 << mle.num_vars; - (0..points).iter().map(|pt| ).collect(); + let terms: Vec<(F, M::Term)> = (0..n).map(|i| { + let bytes = (i as usize).to_le_bytes(); + let mut bits = iter_bits_le(&bytes); - SparsePolynomial::::from_coefficients_vec(mle.num_vars, coeffs) + let mut t: Vec<(usize, usize)> = vec![]; + (0..mle.num_vars).for_each(|j| { + if bits.next().unwrap() { + t.push((j, 1)); + } + }); + + (coeffs[i], M::Term::new(t)) + }).collect(); + + M::from_coefficients_vec(mle.num_vars, terms) } /// Converts a matrix into a (sparse) mle. @@ -77,13 +111,43 @@ pub fn fold_vec_to_mle_low( mod tests { use super::*; + use ark_ec::AdditiveGroup; use ark_poly::Polynomial; + use ark_poly::polynomial::multivariate::{SparsePolynomial, SparseTerm}; use ark_std::{UniformRand, Zero}; use ark_test_curves::bls12_381::{Fr, G1Projective as G}; use crate::r1cs::tests::to_field_sparse; use crate::utils::iter_bits_le; + #[test] + fn test_ext() { + // https://crypto.stackexchange.com/a/84416 + let pts = [Fr::from(10), Fr::from(32), Fr::from(57), Fr::from(81)]; + let exp = [Fr::from(10), Fr::from(22), Fr::from(47), Fr::from(2)]; + + let coeffs = ext(&pts); + + assert_eq!(exp.len(), coeffs.len()); + assert!(exp.iter().zip(coeffs.iter()).all(|(e, c)| e == c)); + } + + #[test] + fn test_mle_to_mvp() { + let pts = [Fr::from(10), Fr::from(32), Fr::from(57), Fr::from(81)]; + let mle = DenseMultilinearExtension::::from_evaluations_slice(2, &pts); + let mvp: SparsePolynomial = mle_to_mvp(&mle); + + let terms = vec![ + (Fr::from(10), SparseTerm::new(vec![])), + (Fr::from(47), SparseTerm::new(vec![(1, 1)])), // mvp repr reorders internally + (Fr::from(22), SparseTerm::new(vec![(0, 1)])), + (Fr::from(2), SparseTerm::new(vec![(0, 1), (1, 1)])), + ]; + + assert!(mvp.terms().iter().enumerate().all(|(e, t)| *t == terms[e])); + } + #[test] fn test_matrix_to_mle() { const NUM_ROWS: usize = 3; diff --git a/nova/src/utils.rs b/nova/src/utils.rs index c0e69556b..36bce8532 100644 --- a/nova/src/utils.rs +++ b/nova/src/utils.rs @@ -44,9 +44,17 @@ pub fn index_to_le_field_encoding(idx: u32, trim: Option) -> let t = ot.unwrap() as usize; assert!(t <= 32); - iter_bits_le(&idx.to_le_bytes()) - .map(|byte| if byte { F::ONE } else { F::ZERO }) - .collect::>()[0..t] + let bytes = idx.to_le_bytes(); + let mut bits = iter_bits_le(&bytes); + (0..t) + .map(|_| { + if bits.next().unwrap() { + F::ONE + } else { + F::ZERO + } + }) + .collect::>() .to_vec() } From 7cf44092a01d9db23f809a270b102b0229aaa79e Mon Sep 17 00:00:00 2001 From: Samuel Judson Date: Thu, 8 Feb 2024 14:42:13 -0500 Subject: [PATCH 12/26] Fix endianness and ranges and get tests passing. --- nova/src/ccs/lccs.rs | 205 +++++++++++++++++++++++++------------------ nova/src/ccs/mle.rs | 38 ++++---- nova/src/utils.rs | 33 +++---- 3 files changed, 159 insertions(+), 117 deletions(-) diff --git a/nova/src/ccs/lccs.rs b/nova/src/ccs/lccs.rs index d79d18a76..e7bb46c47 100644 --- a/nova/src/ccs/lccs.rs +++ b/nova/src/ccs/lccs.rs @@ -13,7 +13,7 @@ use ark_std::ops::Index; use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator}; pub use super::super::sparse::{MatrixRef, SparseMatrix}; -use super::super::utils::index_to_le_field_encoding; +use super::super::utils::index_to_be_field_encoding; use super::mle::{fold_vec_to_mle_low, matrix_to_mle, mle_to_mvp, vec_to_mle}; #[derive(Debug, Copy, Clone, PartialEq)] @@ -154,18 +154,16 @@ impl LCCSShape { .map(|M| M.fix_variables(U.rs.as_slice())) .collect(); - let s = (self.num_io + self.num_vars - 1) - .checked_ilog2() - .unwrap_or(0) - + 1; // s' in papers + let n = (self.num_io + self.num_vars).next_power_of_two(); + let s = (n - 1).checked_ilog2().unwrap_or(0) + 1; // s' in papers - let ys: Vec> = ark_std::cfg_into_iter!(0..s as usize) - .map(|y| index_to_le_field_encoding(y as u32, Some(s))) + let ys: Vec> = ark_std::cfg_into_iter!(0..n) + .map(|y| index_to_be_field_encoding(y as u32, Some(s))) .collect(); let Mzs: Vec = ark_std::cfg_iter!(Mrs) .map(|M| { - (0..s as usize) + (0..n) .map(|y| M.evaluate(&ys[y]) * z.index(y)) .sum() }) @@ -314,12 +312,10 @@ impl< shape: &LCCSShape, commitment_W: &P::Commitment, X: &[G::ScalarField], - rs: Vec, - vs: Vec, + rs: &Vec, + vs: &Vec, ) -> Result { - if X.is_empty() { - return Err(Error::InvalidInputLength); - } else if shape.num_io != X.len() { + if X.is_empty() || shape.num_io != X.len() { Err(Error::InvalidInputLength) } else if ((shape.num_constraints - 1).checked_ilog2().unwrap_or(0) + 1) != rs.len() as u32 { @@ -330,8 +326,8 @@ impl< Ok(Self { commitment_W: commitment_W.clone(), X: X.to_owned(), - rs, - vs, + rs: rs.clone(), + vs: vs.clone(), }) } } @@ -348,7 +344,7 @@ mod tests { use ark_ff::Field; use ark_poly::polynomial::multivariate::{SparsePolynomial, SparseTerm}; use ark_poly_commit::marlin_pst13_pc::MarlinPST13; - use ark_std::rand; + use ark_std::UniformRand; use ark_test_curves::bls12_381::{Bls12_381, G1Projective as G}; @@ -361,7 +357,7 @@ mod tests { type P = MarlinPST13; use crate::r1cs::tests::{to_field_elements, to_field_sparse, A, B, C}; -/* + #[test] fn zero_instance_is_satisfied() -> Result<(), Error> { #[rustfmt::skip] @@ -374,12 +370,12 @@ mod tests { to_field_sparse::(a) }; - let mut rng = rand::thread_rng(); - const NUM_CONSTRAINTS: usize = 3; const NUM_WITNESS: usize = 1; const NUM_PUBLIC: usize = 2; + let mut rng = ark_std::test_rng(); + let lccs_shape = LCCSShape::::new( NUM_CONSTRAINTS, NUM_WITNESS, @@ -400,75 +396,114 @@ mod tests { let commitment_W = witness.commit::(&ck); - let s = (NUM_CONSTRAINTS - 1).checked_ilog2().unwrap_or(0) + 1; + let s1 = (NUM_CONSTRAINTS - 1).checked_ilog2().unwrap_or(0) + 1; + let rs: Vec = (0..s1).map(|_| F::rand(&mut rng)).collect(); - let rs: Vec = (0..s).map(|_| F::random(rng)).collect(); + let z = fold_vec_to_mle_low(&X, &vec_to_mle(&W)); - let instance = LCCSInstance::::new(&lccs_shape, &commitment_W, &X, rs)?; + let Mrs: Vec> = ark_std::cfg_iter!(lccs_shape.Ms) + .map(|M| M.fix_variables(rs.as_slice())) + .collect(); + + let n = (NUM_WITNESS + NUM_PUBLIC).next_power_of_two(); + let s2 = (n - 1).checked_ilog2().unwrap_or(0) + 1; + + let ys: Vec> = ark_std::cfg_into_iter!(0..n) + .map(|y| index_to_be_field_encoding(y as u32, Some(s2))) + .collect(); + + let vs: Vec = ark_std::cfg_iter!(Mrs) + .map(|M| (0..n).map(|y| M.evaluate(&ys[y]) * z.index(y)).sum()) + .collect(); + + let instance = LCCSInstance::::new(&lccs_shape, &commitment_W, &X, &rs, &vs)?; lccs_shape.is_satisfied::(&instance, &witness, &ck)?; + Ok(()) -} - */ - /* - #[test] - fn is_satisfied() -> Result<(), Error> { - let (a, b, c) = { - ( - to_field_sparse::(A), - to_field_sparse::(B), - to_field_sparse::(C), - ) - }; - - const NUM_CONSTRAINTS: usize = 4; - const NUM_WITNESS: usize = 4; - const NUM_PUBLIC: usize = 2; - - let pp = PedersenCommitment::::setup(NUM_WITNESS, &()); - let r1cs_shape: R1CSShape = - R1CSShape::::new(NUM_CONSTRAINTS, NUM_WITNESS, NUM_PUBLIC, &a, &b, &c).unwrap(); - - let ccs_shape = CCSShape::from(r1cs_shape); - - let X = to_field_elements::(&[1, 35]); - let W = to_field_elements::(&[3, 9, 27, 30]); - let commitment_W = PedersenCommitment::::commit(&pp, &W); - - let instance = CCSInstance::>::new(&ccs_shape, &commitment_W, &X)?; - let witness = CCSWitness::::new(&ccs_shape, &W)?; - - ccs_shape.is_satisfied(&instance, &witness, &pp)?; - - // Change commitment. - let invalid_commitment = commitment_W.double(); - let instance = - CCSInstance::>::new(&ccs_shape, &invalid_commitment, &X)?; - assert_eq!( - ccs_shape.is_satisfied(&instance, &witness, &pp), - Err(Error::NotSatisfied) - ); - - // Provide invalid witness. - let invalid_W = to_field_elements::(&[4, 9, 27, 30]); - let commitment_invalid_W = PedersenCommitment::::commit(&pp, &W); - let instance = - CCSInstance::>::new(&ccs_shape, &commitment_invalid_W, &X)?; - let invalid_witness = CCSWitness::::new(&ccs_shape, &invalid_W)?; - assert_eq!( - ccs_shape.is_satisfied(&instance, &invalid_witness, &pp), - Err(Error::NotSatisfied) - ); - - // Provide invalid public input. - let invalid_X = to_field_elements::(&[1, 36]); - let instance = - CCSInstance::>::new(&ccs_shape, &commitment_W, &invalid_X)?; - assert_eq!( - ccs_shape.is_satisfied(&instance, &witness, &pp), - Err(Error::NotSatisfied) - ); - Ok(()) - } - */ + } + + #[test] + fn is_satisfied() -> Result<(), Error> { + let (a, b, c) = { + ( + to_field_sparse::(A), + to_field_sparse::(B), + to_field_sparse::(C), + ) + }; + + const NUM_CONSTRAINTS: usize = 4; + const NUM_WITNESS: usize = 4; + const NUM_PUBLIC: usize = 2; + + let mut rng = ark_std::test_rng(); + + let lccs_shape = LCCSShape::::new( + NUM_CONSTRAINTS, + NUM_WITNESS, + NUM_PUBLIC, + 3, + 2, + 2, + vec![&a, &b, &c], + vec![(F::ONE, vec![0, 1]), (F::ONE.neg(), vec![2])], + )?; + + let X = to_field_elements::(&[1, 35]); + let W = to_field_elements::(&[3, 9, 27, 30]); + let witness = LCCSWitness::::new(&lccs_shape, &W)?; + + let up = P::setup(witness.W.num_vars, Some(witness.W.num_vars), &mut rng).unwrap(); + let (ck, _vk) = P::trim(&up, witness.W.num_vars, 0, None).unwrap(); + + let commitment_W = witness.commit::(&ck); + + let s1 = (NUM_CONSTRAINTS - 1).checked_ilog2().unwrap_or(0) + 1; + let rs: Vec = (0..s1).map(|_| F::rand(&mut rng)).collect(); + + let z = fold_vec_to_mle_low(&X, &vec_to_mle(&W)); + + let Mrs: Vec> = ark_std::cfg_iter!(lccs_shape.Ms) + .map(|M| M.fix_variables(rs.as_slice())) + .collect(); + + let n = (NUM_WITNESS + NUM_PUBLIC).next_power_of_two(); + let s2 = (n - 1).checked_ilog2().unwrap_or(0) + 1; + + let ys: Vec> = ark_std::cfg_into_iter!(0..n) + .map(|y| index_to_be_field_encoding(y as u32, Some(s2))) + .collect(); + + let vs: Vec = ark_std::cfg_iter!(Mrs) + .map(|M| (0..n).map(|y| M.evaluate(&ys[y]) * z.index(y)).sum()) + .collect(); + + let instance = LCCSInstance::::new(&lccs_shape, &commitment_W, &X, &rs, &vs)?; + + lccs_shape.is_satisfied::(&instance, &witness, &ck)?; + + // Provide invalid witness. + let invalid_W = to_field_elements::(&[4, 9, 27, 30]); + let invalid_witness = LCCSWitness::::new(&lccs_shape, &invalid_W)?; + let commitment_invalid_W = invalid_witness.commit::(&ck); + + let instance = + LCCSInstance::::new(&lccs_shape, &commitment_invalid_W, &X, &rs, &vs)?; + assert_eq!( + lccs_shape.is_satisfied(&instance, &invalid_witness, &ck), + Err(Error::NotSatisfied) + ); + + // Provide invalid public input. + let invalid_X = to_field_elements::(&[1, 36]); + let instance = + LCCSInstance::::new(&lccs_shape, &commitment_W, &invalid_X, &rs, &vs)?; + assert_eq!( + lccs_shape.is_satisfied(&instance, &witness, &ck), + Err(Error::NotSatisfied) + ); + + Ok(()) + } } diff --git a/nova/src/ccs/mle.rs b/nova/src/ccs/mle.rs index 1f7204941..ed800499f 100644 --- a/nova/src/ccs/mle.rs +++ b/nova/src/ccs/mle.rs @@ -1,7 +1,10 @@ //! Helper code for multilinear extensions use ark_ff::{Field, PrimeField}; -use ark_poly::{multivariate::Term, DenseMVPolynomial, DenseMultilinearExtension, MultilinearExtension, SparseMultilinearExtension}; +use ark_poly::{ + multivariate::Term, DenseMVPolynomial, DenseMultilinearExtension, MultilinearExtension, + SparseMultilinearExtension, +}; use super::super::sparse::SparseMatrix; use super::super::utils::iter_bits_le; @@ -36,19 +39,21 @@ pub fn mle_to_mvp>(mle: &DenseMultilinear let n = 1 << mle.num_vars; - let terms: Vec<(F, M::Term)> = (0..n).map(|i| { - let bytes = (i as usize).to_le_bytes(); - let mut bits = iter_bits_le(&bytes); + let terms: Vec<(F, M::Term)> = (0..n) + .map(|i| { + let bytes = (i as u32).to_le_bytes(); + let mut bits = iter_bits_le(&bytes); - let mut t: Vec<(usize, usize)> = vec![]; - (0..mle.num_vars).for_each(|j| { - if bits.next().unwrap() { - t.push((j, 1)); - } - }); + let mut t: Vec<(usize, usize)> = vec![]; + (0..mle.num_vars).for_each(|j| { + if bits.next().unwrap() { + t.push((j, 1)); + } + }); - (coeffs[i], M::Term::new(t)) - }).collect(); + (coeffs[i], M::Term::new(t)) + }) + .collect(); M::from_coefficients_vec(mle.num_vars, terms) } @@ -66,7 +71,7 @@ pub fn matrix_to_mle( let s2 = (n - 1).checked_ilog2().unwrap_or(0) + 1; // number of columns in padded matrix - let n = n.next_power_of_two(); + let n = if n == 1 { 2 } else { n.next_power_of_two() }; let evaluations: Vec<(usize, F)> = M.iter().map(|(i, j, value)| ((i * n + j), value)).collect(); @@ -79,7 +84,7 @@ pub fn vec_to_mle(z: &[F]) -> DenseMultilinearExtension { assert!(n > 0); let mut z = z.to_owned(); - z.resize(n.next_power_of_two(), F::zero()); + z.resize(if n == 1 { 2 } else { n.next_power_of_two() }, F::zero()); // compute s' let s = (n - 1).checked_ilog2().unwrap_or(0) + 1; @@ -99,7 +104,7 @@ pub fn fold_vec_to_mle_low( z.extend(&mle.to_evaluations()); n = z.len(); - z.resize(n.next_power_of_two(), F::zero()); + z.resize(if n == 1 { 2 } else { n.next_power_of_two() }, F::zero()); // compute s' let s = (n - 1).checked_ilog2().unwrap_or(0) + 1; @@ -111,9 +116,8 @@ pub fn fold_vec_to_mle_low( mod tests { use super::*; - use ark_ec::AdditiveGroup; - use ark_poly::Polynomial; use ark_poly::polynomial::multivariate::{SparsePolynomial, SparseTerm}; + use ark_poly::Polynomial; use ark_std::{UniformRand, Zero}; use ark_test_curves::bls12_381::{Fr, G1Projective as G}; diff --git a/nova/src/utils.rs b/nova/src/utils.rs index 36bce8532..d762ec8ca 100644 --- a/nova/src/utils.rs +++ b/nova/src/utils.rs @@ -35,10 +35,10 @@ pub fn iter_bits_le(bytes: &[u8]) -> impl Iterator + '_ { .flat_map(|byte| (0..8).map(move |bit| ((1 << bit) & byte) != 0)) } -/// Returns field encoded bits in little-endian order. -pub fn index_to_le_field_encoding(idx: u32, trim: Option) -> Vec { +/// Returns field encoded bits in big-endian order. +pub fn index_to_be_field_encoding(idx: u32, trim: Option) -> Vec { let mut ot = trim; - if let None = ot { + if ot.is_none() { ot = Some(32); } let t = ot.unwrap() as usize; @@ -46,7 +46,8 @@ pub fn index_to_le_field_encoding(idx: u32, trim: Option) -> let bytes = idx.to_le_bytes(); let mut bits = iter_bits_le(&bytes); - (0..t) + + let mut enc = (0..t) .map(|_| { if bits.next().unwrap() { F::ONE @@ -54,8 +55,10 @@ pub fn index_to_le_field_encoding(idx: u32, trim: Option) -> F::ZERO } }) - .collect::>() - .to_vec() + .collect::>(); + + enc.reverse(); + enc } #[cfg(test)] @@ -82,24 +85,21 @@ mod tests { } #[test] - fn index_le_field_encoding() { + fn index_be_field_encoding() { const X: u32 = 13; // 1101 assert_eq!( - super::index_to_le_field_encoding::(X, Some(4)), - [Fr::ONE, Fr::ZERO, Fr::ONE, Fr::ONE] + super::index_to_be_field_encoding::(X, Some(4)), + [Fr::ONE, Fr::ONE, Fr::ZERO, Fr::ONE] ); assert_eq!( - super::index_to_le_field_encoding::(X, Some(5)), - [Fr::ONE, Fr::ZERO, Fr::ONE, Fr::ONE, Fr::ZERO] + super::index_to_be_field_encoding::(X, Some(5)), + [Fr::ZERO, Fr::ONE, Fr::ONE, Fr::ZERO, Fr::ONE] ); assert_eq!( - super::index_to_le_field_encoding::(X, None), + super::index_to_be_field_encoding::(X, None), [ - Fr::ONE, Fr::ZERO, - Fr::ONE, - Fr::ONE, Fr::ZERO, Fr::ZERO, Fr::ZERO, @@ -127,7 +127,10 @@ mod tests { Fr::ZERO, Fr::ZERO, Fr::ZERO, + Fr::ONE, + Fr::ONE, Fr::ZERO, + Fr::ONE, ] ); } From 3d0ac3292eb7f2df2ee80e29c8e5c6aa73138144 Mon Sep 17 00:00:00 2001 From: Samuel Judson Date: Thu, 8 Feb 2024 14:44:54 -0500 Subject: [PATCH 13/26] Fix formatting. --- nova/src/ccs/lccs.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/nova/src/ccs/lccs.rs b/nova/src/ccs/lccs.rs index e7bb46c47..eb770e17b 100644 --- a/nova/src/ccs/lccs.rs +++ b/nova/src/ccs/lccs.rs @@ -162,11 +162,7 @@ impl LCCSShape { .collect(); let Mzs: Vec = ark_std::cfg_iter!(Mrs) - .map(|M| { - (0..n) - .map(|y| M.evaluate(&ys[y]) * z.index(y)) - .sum() - }) + .map(|M| (0..n).map(|y| M.evaluate(&ys[y]) * z.index(y)).sum()) .collect(); if ark_std::cfg_into_iter!(0..self.num_matrices).any(|idx| Mzs[idx] != U.vs[idx]) { From 6d193c3a27a71826dd522936c3e35c6b0b82a8d6 Mon Sep 17 00:00:00 2001 From: Samuel Judson Date: Thu, 8 Feb 2024 23:15:35 -0500 Subject: [PATCH 14/26] Realized there's a better way to invoke the partially fixed polynomial. --- nova/src/ccs/lccs.rs | 37 ++++++++++---------- nova/src/utils.rs | 81 +------------------------------------------- 2 files changed, 20 insertions(+), 98 deletions(-) diff --git a/nova/src/ccs/lccs.rs b/nova/src/ccs/lccs.rs index eb770e17b..af5eca87a 100644 --- a/nova/src/ccs/lccs.rs +++ b/nova/src/ccs/lccs.rs @@ -1,8 +1,7 @@ use ark_crypto_primitives::sponge::CryptographicSponge; use ark_ec::{AdditiveGroup, CurveGroup}; use ark_poly::{ - DenseMVPolynomial, DenseMultilinearExtension, MultilinearExtension, Polynomial, - SparseMultilinearExtension, + DenseMVPolynomial, DenseMultilinearExtension, MultilinearExtension, SparseMultilinearExtension, }; use ark_poly_commit::{LabeledPolynomial, PolynomialCommitment}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; @@ -13,7 +12,6 @@ use ark_std::ops::Index; use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator}; pub use super::super::sparse::{MatrixRef, SparseMatrix}; -use super::super::utils::index_to_be_field_encoding; use super::mle::{fold_vec_to_mle_low, matrix_to_mle, mle_to_mvp, vec_to_mle}; #[derive(Debug, Copy, Clone, PartialEq)] @@ -155,14 +153,15 @@ impl LCCSShape { .collect(); let n = (self.num_io + self.num_vars).next_power_of_two(); - let s = (n - 1).checked_ilog2().unwrap_or(0) + 1; // s' in papers - let ys: Vec> = ark_std::cfg_into_iter!(0..n) - .map(|y| index_to_be_field_encoding(y as u32, Some(s))) - .collect(); + let shift = usize::BITS - ((n - 1).checked_ilog2().unwrap_or(0) + 1); // for fixing endianness let Mzs: Vec = ark_std::cfg_iter!(Mrs) - .map(|M| (0..n).map(|y| M.evaluate(&ys[y]) * z.index(y)).sum()) + .map(|M| { + (0..n) + .map(|y| *M.index(y.reverse_bits() >> shift) * z.index(y)) + .sum() + }) .collect(); if ark_std::cfg_into_iter!(0..self.num_matrices).any(|idx| Mzs[idx] != U.vs[idx]) { @@ -402,14 +401,15 @@ mod tests { .collect(); let n = (NUM_WITNESS + NUM_PUBLIC).next_power_of_two(); - let s2 = (n - 1).checked_ilog2().unwrap_or(0) + 1; - let ys: Vec> = ark_std::cfg_into_iter!(0..n) - .map(|y| index_to_be_field_encoding(y as u32, Some(s2))) - .collect(); + let shift = usize::BITS - ((n - 1).checked_ilog2().unwrap_or(0) + 1); let vs: Vec = ark_std::cfg_iter!(Mrs) - .map(|M| (0..n).map(|y| M.evaluate(&ys[y]) * z.index(y)).sum()) + .map(|M| { + (0..n) + .map(|y| *M.index(y.reverse_bits() >> shift) * z.index(y)) + .sum() + }) .collect(); let instance = LCCSInstance::::new(&lccs_shape, &commitment_W, &X, &rs, &vs)?; @@ -465,14 +465,15 @@ mod tests { .collect(); let n = (NUM_WITNESS + NUM_PUBLIC).next_power_of_two(); - let s2 = (n - 1).checked_ilog2().unwrap_or(0) + 1; - let ys: Vec> = ark_std::cfg_into_iter!(0..n) - .map(|y| index_to_be_field_encoding(y as u32, Some(s2))) - .collect(); + let shift = usize::BITS - ((n - 1).checked_ilog2().unwrap_or(0) + 1); let vs: Vec = ark_std::cfg_iter!(Mrs) - .map(|M| (0..n).map(|y| M.evaluate(&ys[y]) * z.index(y)).sum()) + .map(|M| { + (0..n) + .map(|y| *M.index(y.reverse_bits() >> shift) * z.index(y)) + .sum() + }) .collect(); let instance = LCCSInstance::::new(&lccs_shape, &commitment_W, &X, &rs, &vs)?; diff --git a/nova/src/utils.rs b/nova/src/utils.rs index d762ec8ca..7d402c213 100644 --- a/nova/src/utils.rs +++ b/nova/src/utils.rs @@ -35,37 +35,9 @@ pub fn iter_bits_le(bytes: &[u8]) -> impl Iterator + '_ { .flat_map(|byte| (0..8).map(move |bit| ((1 << bit) & byte) != 0)) } -/// Returns field encoded bits in big-endian order. -pub fn index_to_be_field_encoding(idx: u32, trim: Option) -> Vec { - let mut ot = trim; - if ot.is_none() { - ot = Some(32); - } - let t = ot.unwrap() as usize; - assert!(t <= 32); - - let bytes = idx.to_le_bytes(); - let mut bits = iter_bits_le(&bytes); - - let mut enc = (0..t) - .map(|_| { - if bits.next().unwrap() { - F::ONE - } else { - F::ZERO - } - }) - .collect::>(); - - enc.reverse(); - enc -} - #[cfg(test)] mod tests { - use ark_ec::AdditiveGroup; - use ark_ff::{BigInteger, Field, PrimeField}; - use ark_pallas::Fr; + use ark_ff::{BigInteger, PrimeField}; use ark_std::UniformRand; type BigInt = ::BigInt; @@ -83,55 +55,4 @@ mod tests { assert_eq!(BigInt::from_bits_le(&bits), big_int); } } - - #[test] - fn index_be_field_encoding() { - const X: u32 = 13; // 1101 - - assert_eq!( - super::index_to_be_field_encoding::(X, Some(4)), - [Fr::ONE, Fr::ONE, Fr::ZERO, Fr::ONE] - ); - assert_eq!( - super::index_to_be_field_encoding::(X, Some(5)), - [Fr::ZERO, Fr::ONE, Fr::ONE, Fr::ZERO, Fr::ONE] - ); - assert_eq!( - super::index_to_be_field_encoding::(X, None), - [ - Fr::ZERO, - Fr::ZERO, - Fr::ZERO, - Fr::ZERO, - Fr::ZERO, - Fr::ZERO, - Fr::ZERO, - Fr::ZERO, - Fr::ZERO, - Fr::ZERO, - Fr::ZERO, - Fr::ZERO, - Fr::ZERO, - Fr::ZERO, - Fr::ZERO, - Fr::ZERO, - Fr::ZERO, - Fr::ZERO, - Fr::ZERO, - Fr::ZERO, - Fr::ZERO, - Fr::ZERO, - Fr::ZERO, - Fr::ZERO, - Fr::ZERO, - Fr::ZERO, - Fr::ZERO, - Fr::ZERO, - Fr::ONE, - Fr::ONE, - Fr::ZERO, - Fr::ONE, - ] - ); - } } From 591358c56d4599554c8bcbd30f75b6bf023b77db Mon Sep 17 00:00:00 2001 From: Samuel Judson Date: Tue, 13 Feb 2024 09:56:01 -0500 Subject: [PATCH 15/26] Update interfaces and some additional reworking. --- nova/src/ccs/lccs.rs | 148 +++++++++++++------------------------------ nova/src/ccs/mle.rs | 31 ++++++--- nova/src/ccs/mod.rs | 5 -- 3 files changed, 66 insertions(+), 118 deletions(-) diff --git a/nova/src/ccs/lccs.rs b/nova/src/ccs/lccs.rs index af5eca87a..eb2c577ac 100644 --- a/nova/src/ccs/lccs.rs +++ b/nova/src/ccs/lccs.rs @@ -1,5 +1,6 @@ use ark_crypto_primitives::sponge::CryptographicSponge; use ark_ec::{AdditiveGroup, CurveGroup}; +use ark_ff::Field; use ark_poly::{ DenseMVPolynomial, DenseMultilinearExtension, MultilinearExtension, SparseMultilinearExtension, }; @@ -7,24 +8,22 @@ use ark_poly_commit::{LabeledPolynomial, PolynomialCommitment}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::ops::Index; +use std::ops::Neg; #[cfg(feature = "parallel")] use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator}; +use super::super::r1cs::R1CSShape; pub use super::super::sparse::{MatrixRef, SparseMatrix}; use super::mle::{fold_vec_to_mle_low, matrix_to_mle, mle_to_mvp, vec_to_mle}; #[derive(Debug, Copy, Clone, PartialEq)] pub enum Error { - ConstraintNumberMismatch, - InputLengthMismatch, InvalidWitnessLength, InvalidInputLength, - InvalidConversion, - InvalidMultiset, - MultisetCardinalityMismatch, InvalidEvaluationPoint, InvalidTargets, + FailedWitnessCommitting, NotSatisfied, } @@ -59,78 +58,6 @@ pub struct LCCSShape { } impl LCCSShape { - fn validate( - num_constraints: usize, - num_vars: usize, - num_io: usize, - M: MatrixRef<'_, G::ScalarField>, - ) -> Result<(), Error> { - for (i, row) in M.iter().enumerate() { - for (_value, j) in row { - if i >= num_constraints { - return Err(Error::ConstraintNumberMismatch); - } - if *j >= num_io + num_vars { - return Err(Error::InputLengthMismatch); - } - } - } - - Ok(()) - } - - /// Create an object of type `LCCSShape` from the explicitly specified CCS matrices - pub fn new( - num_constraints: usize, - num_vars: usize, - num_io: usize, - num_matrices: usize, - num_multisets: usize, - max_cardinality: usize, - Ms: Vec>, - cSs: Vec<(G::ScalarField, Vec)>, - ) -> Result, Error> { - if num_io == 0 { - return Err(Error::InvalidInputLength); - } - - Ms.iter() - .try_for_each(|M| Self::validate(num_constraints, num_vars, num_io, M))?; - - assert_eq!(Ms.len(), num_matrices); - assert_eq!(cSs.len(), num_multisets); - - for (_c, S) in cSs.iter() { - if S.len() > max_cardinality { - return Err(Error::MultisetCardinalityMismatch); - } - - S.iter().try_for_each(|idx| { - if idx >= &num_matrices { - Err(Error::InvalidMultiset) - } else { - Ok(()) - } - })?; - } - - let rows = num_constraints; - let columns = num_io + num_vars; - Ok(Self { - num_constraints, - num_vars, - num_io, - num_matrices, - num_multisets, - max_cardinality, - Ms: Ms - .iter() - .map(|M| matrix_to_mle(rows, columns, &SparseMatrix::new(M, rows, columns))) - .collect(), - cSs, - }) - } - pub fn is_satisfied< M: DenseMVPolynomial, S: CryptographicSponge, @@ -177,11 +104,41 @@ impl LCCSShape { None, ); - if U.commitment_W != *P::commit(ck, &[lab_W], None).unwrap().0[0].commitment() { - return Err(Error::NotSatisfied); + if let Ok(commit) = P::commit(ck, &[lab_W], None) { + if U.commitment_W != *commit.0[0].commitment() { + return Err(Error::NotSatisfied); + } + + Ok(()) + } else { + Err(Error::FailedWitnessCommitting) } + } +} - Ok(()) +/// Create an object of type `LCCSShape` from the specified R1CS shape +impl From> for LCCSShape { + fn from(shape: R1CSShape) -> Self { + let rows = shape.num_constraints; + let columns = shape.num_io + shape.num_vars; + + Self { + num_constraints: shape.num_constraints, + num_io: shape.num_io, + num_vars: shape.num_vars, + num_matrices: 3, + num_multisets: 2, + max_cardinality: 2, + Ms: vec![ + matrix_to_mle(rows, columns, &shape.A), + matrix_to_mle(rows, columns, &shape.B), + matrix_to_mle(rows, columns, &shape.C), + ], + cSs: vec![ + (G::ScalarField::ONE, vec![0, 1]), + (G::ScalarField::ONE.neg(), vec![2]), + ], + } } } @@ -336,15 +293,12 @@ mod tests { use super::*; use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; - use ark_ff::Field; use ark_poly::polynomial::multivariate::{SparsePolynomial, SparseTerm}; use ark_poly_commit::marlin_pst13_pc::MarlinPST13; use ark_std::UniformRand; use ark_test_curves::bls12_381::{Bls12_381, G1Projective as G}; - use std::ops::Neg; - type F = as ark_ec::PrimeGroup>::ScalarField; type M = SparsePolynomial; // confusingly, the DenseMVPolynomial trait is only implemented by SparsePolynomial @@ -371,16 +325,10 @@ mod tests { let mut rng = ark_std::test_rng(); - let lccs_shape = LCCSShape::::new( - NUM_CONSTRAINTS, - NUM_WITNESS, - NUM_PUBLIC, - 3, - 2, - 2, - vec![&a, &a, &a], - vec![(F::ONE, vec![0, 1]), (F::ONE.neg(), vec![2])], - )?; + let r1cs_shape: R1CSShape = + R1CSShape::::new(NUM_CONSTRAINTS, NUM_WITNESS, NUM_PUBLIC, &a, &a, &a).unwrap(); + + let lccs_shape = LCCSShape::from(r1cs_shape); let X = to_field_elements::(&[0, 0]); let W = to_field_elements::(&[0]); @@ -435,16 +383,10 @@ mod tests { let mut rng = ark_std::test_rng(); - let lccs_shape = LCCSShape::::new( - NUM_CONSTRAINTS, - NUM_WITNESS, - NUM_PUBLIC, - 3, - 2, - 2, - vec![&a, &b, &c], - vec![(F::ONE, vec![0, 1]), (F::ONE.neg(), vec![2])], - )?; + let r1cs_shape: R1CSShape = + R1CSShape::::new(NUM_CONSTRAINTS, NUM_WITNESS, NUM_PUBLIC, &a, &b, &c).unwrap(); + + let lccs_shape = LCCSShape::from(r1cs_shape); let X = to_field_elements::(&[1, 35]); let W = to_field_elements::(&[3, 9, 27, 30]); diff --git a/nova/src/ccs/mle.rs b/nova/src/ccs/mle.rs index ed800499f..eee29fe8a 100644 --- a/nova/src/ccs/mle.rs +++ b/nova/src/ccs/mle.rs @@ -10,17 +10,20 @@ use super::super::sparse::SparseMatrix; use super::super::utils::iter_bits_le; /// Utility function for mle -> mvp conversion. -fn ext(pts: &[F]) -> Vec { +fn compute_coeffs_from_evals(pts: &[F]) -> Vec { let n = pts.len(); - // https://crypto.stackexchange.com/a/84416 + // This code recursively implements sum over subsets to compute + // the terms of the polynomial from its defining evaluations. + // + // see: https://crypto.stackexchange.com/a/84416 if n == 1 { return pts.to_vec(); } let h = n / 2; - let l = ext(&pts[0..h]); - let r = ext(&pts[h..n]); + let l = compute_coeffs_from_evals(&pts[0..h]); + let r = compute_coeffs_from_evals(&pts[h..n]); [ l.clone(), @@ -35,7 +38,7 @@ fn ext(pts: &[F]) -> Vec { /// Converts an mle into a generic multivariate polynomial. pub fn mle_to_mvp>(mle: &DenseMultilinearExtension) -> M { let evals = mle.to_evaluations(); - let coeffs = ext(evals.as_slice()); + let coeffs = compute_coeffs_from_evals(evals.as_slice()); let n = 1 << mle.num_vars; @@ -125,12 +128,17 @@ mod tests { use crate::utils::iter_bits_le; #[test] - fn test_ext() { - // https://crypto.stackexchange.com/a/84416 + fn test_coeffs_from_evals() { + // evaluations vector: [ 10, 32, 57, 81 ] + // encoded multilinear polynomial by evaluations: 10(1-x)(1-y) + 32x(1-y) + 57(1-x)y + 81xy + // ... multiply out and collect terms... + // encoded multilinear polynomial by coefficients: 10 + 22x + 47y + 2xy + // + // from: https://crypto.stackexchange.com/a/84416 let pts = [Fr::from(10), Fr::from(32), Fr::from(57), Fr::from(81)]; let exp = [Fr::from(10), Fr::from(22), Fr::from(47), Fr::from(2)]; - let coeffs = ext(&pts); + let coeffs = compute_coeffs_from_evals(&pts); assert_eq!(exp.len(), coeffs.len()); assert!(exp.iter().zip(coeffs.iter()).all(|(e, c)| e == c)); @@ -142,14 +150,17 @@ mod tests { let mle = DenseMultilinearExtension::::from_evaluations_slice(2, &pts); let mvp: SparsePolynomial = mle_to_mvp(&mle); - let terms = vec![ + let exp = vec![ (Fr::from(10), SparseTerm::new(vec![])), (Fr::from(47), SparseTerm::new(vec![(1, 1)])), // mvp repr reorders internally (Fr::from(22), SparseTerm::new(vec![(0, 1)])), (Fr::from(2), SparseTerm::new(vec![(0, 1), (1, 1)])), ]; - assert!(mvp.terms().iter().enumerate().all(|(e, t)| *t == terms[e])); + let terms = mvp.terms(); + + assert_eq!(exp.len(), terms.len()); + assert!(exp.iter().zip(terms.iter()).all(|(e, t)| e == t)); } #[test] diff --git a/nova/src/ccs/mod.rs b/nova/src/ccs/mod.rs index 6ac3465ba..8b03c28f4 100644 --- a/nova/src/ccs/mod.rs +++ b/nova/src/ccs/mod.rs @@ -20,13 +20,8 @@ pub mod mle; #[derive(Debug, Copy, Clone, PartialEq)] pub enum Error { - ConstraintNumberMismatch, - InputLengthMismatch, InvalidWitnessLength, InvalidInputLength, - InvalidConversion, - InvalidMultiset, - MultisetCardinalityMismatch, NotSatisfied, } From 0a310ad9a8c571a31231ef4ad48a2242de7323ff Mon Sep 17 00:00:00 2001 From: Samuel Judson Date: Tue, 13 Feb 2024 10:15:47 -0500 Subject: [PATCH 16/26] Resolve clippy. --- nova/src/ccs/lccs.rs | 8 ++++---- nova/src/ccs/mle.rs | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/nova/src/ccs/lccs.rs b/nova/src/ccs/lccs.rs index eb2c577ac..0e853f7d0 100644 --- a/nova/src/ccs/lccs.rs +++ b/nova/src/ccs/lccs.rs @@ -264,8 +264,8 @@ impl< shape: &LCCSShape, commitment_W: &P::Commitment, X: &[G::ScalarField], - rs: &Vec, - vs: &Vec, + rs: &[G::ScalarField], + vs: &[G::ScalarField], ) -> Result { if X.is_empty() || shape.num_io != X.len() { Err(Error::InvalidInputLength) @@ -278,8 +278,8 @@ impl< Ok(Self { commitment_W: commitment_W.clone(), X: X.to_owned(), - rs: rs.clone(), - vs: vs.clone(), + rs: rs.to_owned(), + vs: vs.to_owned(), }) } } diff --git a/nova/src/ccs/mle.rs b/nova/src/ccs/mle.rs index eee29fe8a..399c148ec 100644 --- a/nova/src/ccs/mle.rs +++ b/nova/src/ccs/mle.rs @@ -184,21 +184,21 @@ mod tests { let m = NUM_ROWS.next_power_of_two(); let n = NUM_COLS.next_power_of_two(); - for i in 0..m { - for j in 0..n { + for (i, row) in M.iter().enumerate().take(m) { + for (j, entry) in row.iter().enumerate().take(n) { let row_mask = (1 << 3) * i; // shift column bits let _j = j | row_mask; let j_bytes = _j.to_le_bytes(); let j_bits: Vec = iter_bits_le(j_bytes.as_slice()) - .map(|b| Fr::from(b)) + .map(Fr::from) .take(NUM_VARS) .collect(); let eval = mle.evaluate(&j_bits); let expected = if i < NUM_ROWS && j < NUM_COLS { - M[i][j].into() + (*entry).into() } else { Fr::zero() }; @@ -217,16 +217,16 @@ mod tests { let mle = vec_to_mle(&z); let n = LEN.next_power_of_two(); - for i in 0..n { + for (i, entry) in z.iter().enumerate().take(n) { let i_bytes = i.to_le_bytes(); let i_bits: Vec = iter_bits_le(i_bytes.as_slice()) - .map(|b| Fr::from(b)) + .map(Fr::from) .take(NUM_VARS) .collect(); let eval = mle.evaluate(&i_bits); - let expected = if i < LEN { z[i].into() } else { Fr::zero() }; + let expected = if i < LEN { *entry } else { Fr::zero() }; assert_eq!(eval, expected); } } @@ -249,16 +249,16 @@ mod tests { for i in 0..n { let i_bytes = i.to_le_bytes(); let i_bits: Vec = iter_bits_le(i_bytes.as_slice()) - .map(|b| Fr::from(b)) + .map(Fr::from) .take(NUM_VARS) .collect(); let eval = mle2.evaluate(&i_bits); - if i < VEC_LEN { - assert_eq!(eval, z2[i].into()); - } else if i >= VEC_LEN && i < TOT_LEN { - assert_eq!(eval, z1[i - VEC_LEN].into()); + if (0..VEC_LEN).contains(&i) { + assert_eq!(eval, z2[i]); + } else if (VEC_LEN..TOT_LEN).contains(&i) { + assert_eq!(eval, z1[i - VEC_LEN]); } else { assert_eq!(eval, Fr::zero()); } From 54fa35abac2ff6a02da92ba1788dddfb45713f48 Mon Sep 17 00:00:00 2001 From: Samuel Judson Date: Thu, 15 Feb 2024 09:51:10 -0500 Subject: [PATCH 17/26] Unify shapes. --- nova/src/ccs/mod.rs | 372 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 370 insertions(+), 2 deletions(-) diff --git a/nova/src/ccs/mod.rs b/nova/src/ccs/mod.rs index 8b03c28f4..a0ac21a31 100644 --- a/nova/src/ccs/mod.rs +++ b/nova/src/ccs/mod.rs @@ -1,13 +1,20 @@ +use ark_crypto_primitives::sponge::CryptographicSponge; use ark_ec::{AdditiveGroup, CurveGroup}; use ark_ff::Field; +use ark_poly::{ + DenseMVPolynomial, DenseMultilinearExtension, MultilinearExtension, SparseMultilinearExtension, +}; +use ark_poly_commit::{LabeledPolynomial, PolynomialCommitment}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::ops::Index; use ark_std::Zero; use std::ops::Neg; #[cfg(feature = "parallel")] use rayon::iter::{ - IndexedParallelIterator, IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator, + IndexedParallelIterator, IntoParallelIterator, IntoParallelRefIterator, + IntoParallelRefMutIterator, ParallelIterator, }; use super::commitment::CommitmentScheme; @@ -15,13 +22,16 @@ use super::commitment::CommitmentScheme; use super::r1cs::R1CSShape; pub use super::sparse::{MatrixRef, SparseMatrix}; -pub mod lccs; pub mod mle; +use mle::{fold_vec_to_mle_low, matrix_to_mle, mle_to_mvp, vec_to_mle}; #[derive(Debug, Copy, Clone, PartialEq)] pub enum Error { InvalidWitnessLength, InvalidInputLength, + InvalidEvaluationPoint, + InvalidTargets, + FailedWitnessCommitting, NotSatisfied, } @@ -98,6 +108,65 @@ impl CCSShape { Ok(()) } + + pub fn is_satisfied_linearized< + M: DenseMVPolynomial, + S: CryptographicSponge, + P: PolynomialCommitment, + >( + &self, + U: &LCCSInstance, + W: &LCCSWitness, + ck: &P::CommitterKey, + ) -> Result<(), Error> + where + P::Commitment: PartialEq, + { + assert_eq!(U.X.len(), self.num_io); + + let z: DenseMultilinearExtension = fold_vec_to_mle_low(&U.X, &W.W); + + let rows = self.num_constraints; + let columns = self.num_io + self.num_vars; + + let Mrs: Vec> = ark_std::cfg_iter!(&self.Ms) + .map(|M| matrix_to_mle(rows, columns, M).fix_variables(U.rs.as_slice())) + .collect(); + + let n = columns.next_power_of_two(); + let shift = usize::BITS - ((n - 1).checked_ilog2().unwrap_or(0) + 1); // for fixing endianness + + let Mzs: Vec = ark_std::cfg_iter!(Mrs) + .map(|M| { + (0..n) + .map(|y| *M.index(y.reverse_bits() >> shift) * z.index(y)) + .sum() + }) + .collect(); + + if ark_std::cfg_into_iter!(0..self.num_matrices).any(|idx| Mzs[idx] != U.vs[idx]) { + return Err(Error::NotSatisfied); + } + + let mvp_W: M = mle_to_mvp::(&W.W); + + let lab_W = LabeledPolynomial::::new( + "witness".to_string(), + mvp_W, + Some(W.W.num_vars), + None, + ); + + if let Ok(commit) = P::commit(ck, &[lab_W], None) { + if U.commitment_W != *commit.0[0].commitment() { + return Err(Error::NotSatisfied); + } + + Ok(()) + } else { + Err(Error::FailedWitnessCommitting) + } + } } /// Create an object of type `CCSShape` from the specified R1CS shape @@ -194,6 +263,149 @@ impl> CCSInstance { } } +/// A type that holds a witness for a given LCCS instance. +#[derive(Clone, Debug, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)] +pub struct LCCSWitness { + pub W: DenseMultilinearExtension, +} + +/// A type that holds an LCCS instance. +#[derive(CanonicalSerialize, CanonicalDeserialize)] +pub struct LCCSInstance< + G: CurveGroup, + M: DenseMVPolynomial, + S: CryptographicSponge, + P: PolynomialCommitment, +> { + /// Commitment to MLE of witness. + /// + /// C in HyperNova/CCS papers. + pub commitment_W: P::Commitment, + /// X is assumed to start with a `ScalarField` field element `u`. + pub X: Vec, + /// (Random) evaluation point + pub rs: Vec, + /// Evaluation targets + pub vs: Vec, +} + +impl< + G: CurveGroup, + M: DenseMVPolynomial, + S: CryptographicSponge, + P: PolynomialCommitment, + > Clone for LCCSInstance +{ + fn clone(&self) -> Self { + Self { + commitment_W: self.commitment_W.clone(), + X: self.X.clone(), + rs: self.rs.clone(), + vs: self.vs.clone(), + } + } +} + +impl< + G: CurveGroup, + M: DenseMVPolynomial, + S: CryptographicSponge, + P: PolynomialCommitment, + > PartialEq for LCCSInstance +where + P::Commitment: PartialEq, +{ + fn eq(&self, other: &Self) -> bool + where + P::Commitment: PartialEq, + { + self.commitment_W == other.commitment_W && self.X == other.X + } +} + +impl< + G: CurveGroup, + M: DenseMVPolynomial, + S: CryptographicSponge, + P: PolynomialCommitment, + > Eq for LCCSInstance +where + P::Commitment: Eq, +{ +} + +impl LCCSWitness { + /// A method to create a witness object using a vector of scalars. + pub fn new(shape: &CCSShape, W: &[G::ScalarField]) -> Result { + if shape.num_vars != W.len() { + Err(Error::InvalidWitnessLength) + } else { + Ok(Self { W: vec_to_mle(W) }) + } + } + + pub fn zero(shape: &CCSShape) -> Self { + Self { + W: vec_to_mle(vec![G::ScalarField::ZERO; shape.num_vars].as_slice()), + } + } + + /// Commits to the witness using the supplied key + pub fn commit< + M: DenseMVPolynomial, + S: CryptographicSponge, + P: PolynomialCommitment, + >( + &self, + ck: &P::CommitterKey, + ) -> P::Commitment { + let mvp_W: M = mle_to_mvp::(&self.W); + + let lab_W = LabeledPolynomial::::new( + "witness".to_string(), + mvp_W, + Some(self.W.num_vars), + None, + ); + + let wc = P::commit(ck, &[lab_W], None).unwrap(); + wc.0[0].commitment().clone() + } +} + +impl< + G: CurveGroup, + M: DenseMVPolynomial, + S: CryptographicSponge, + P: PolynomialCommitment, + > LCCSInstance +{ + /// A method to create an instance object using constituent elements. + pub fn new( + shape: &CCSShape, + commitment_W: &P::Commitment, + X: &[G::ScalarField], + rs: &[G::ScalarField], + vs: &[G::ScalarField], + ) -> Result { + if X.is_empty() || shape.num_io != X.len() { + Err(Error::InvalidInputLength) + } else if ((shape.num_constraints - 1).checked_ilog2().unwrap_or(0) + 1) != rs.len() as u32 + { + Err(Error::InvalidEvaluationPoint) + } else if shape.num_matrices != vs.len() { + Err(Error::InvalidTargets) + } else { + Ok(Self { + commitment_W: commitment_W.clone(), + X: X.to_owned(), + rs: rs.to_owned(), + vs: vs.to_owned(), + }) + } + } +} + #[cfg(test)] mod tests { #![allow(non_upper_case_globals)] @@ -298,4 +510,160 @@ mod tests { ); Ok(()) } + + use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; + use ark_poly::polynomial::multivariate::{SparsePolynomial, SparseTerm}; + use ark_poly_commit::marlin_pst13_pc::MarlinPST13; + use ark_std::UniformRand; + + use ark_test_curves::bls12_381::Bls12_381; + + type F = as ark_ec::PrimeGroup>::ScalarField; + + type M = SparsePolynomial; // confusingly, the DenseMVPolynomial trait is only implemented by SparsePolynomial + type S = PoseidonSponge; + type P = MarlinPST13; + + #[test] + fn zero_instance_is_satisfied_linearized() -> Result<(), Error> { + #[rustfmt::skip] + let a = { + let a: &[&[u64]] = &[ + &[1, 2, 3], + &[3, 4, 5], + &[6, 7, 8], + ]; + to_field_sparse::(a) + }; + + const NUM_CONSTRAINTS: usize = 3; + const NUM_WITNESS: usize = 1; + const NUM_PUBLIC: usize = 2; + + let mut rng = ark_std::test_rng(); + + let r1cs_shape: R1CSShape = + R1CSShape::::new(NUM_CONSTRAINTS, NUM_WITNESS, NUM_PUBLIC, &a, &a, &a).unwrap(); + + let ccs_shape = CCSShape::from(r1cs_shape); + + let X = to_field_elements::(&[0, 0]); + let W = to_field_elements::(&[0]); + let witness = LCCSWitness::::new(&ccs_shape, &W)?; + + let up = P::setup(witness.W.num_vars, Some(witness.W.num_vars), &mut rng).unwrap(); + let (ck, _vk) = P::trim(&up, witness.W.num_vars, 0, None).unwrap(); + + let commitment_W = witness.commit::(&ck); + + let s1 = (NUM_CONSTRAINTS - 1).checked_ilog2().unwrap_or(0) + 1; + let rs: Vec = (0..s1).map(|_| F::rand(&mut rng)).collect(); + + let z = fold_vec_to_mle_low(&X, &vec_to_mle(&W)); + + let rows = NUM_CONSTRAINTS; + let columns = NUM_WITNESS + NUM_PUBLIC; + + let Mrs: Vec> = ark_std::cfg_iter!(ccs_shape.Ms) + .map(|M| matrix_to_mle(rows, columns, M).fix_variables(rs.as_slice())) + .collect(); + + let n = columns.next_power_of_two(); + let shift = usize::BITS - ((n - 1).checked_ilog2().unwrap_or(0) + 1); + + let vs: Vec = ark_std::cfg_iter!(Mrs) + .map(|M| { + (0..n) + .map(|y| *M.index(y.reverse_bits() >> shift) * z.index(y)) + .sum() + }) + .collect(); + + let instance = LCCSInstance::::new(&ccs_shape, &commitment_W, &X, &rs, &vs)?; + + ccs_shape.is_satisfied_linearized::(&instance, &witness, &ck)?; + + Ok(()) + } + + #[test] + fn is_satisfied_linearized() -> Result<(), Error> { + let (a, b, c) = { + ( + to_field_sparse::(A), + to_field_sparse::(B), + to_field_sparse::(C), + ) + }; + + const NUM_CONSTRAINTS: usize = 4; + const NUM_WITNESS: usize = 4; + const NUM_PUBLIC: usize = 2; + + let mut rng = ark_std::test_rng(); + + let r1cs_shape: R1CSShape = + R1CSShape::::new(NUM_CONSTRAINTS, NUM_WITNESS, NUM_PUBLIC, &a, &b, &c).unwrap(); + + let ccs_shape = CCSShape::from(r1cs_shape); + + let X = to_field_elements::(&[1, 35]); + let W = to_field_elements::(&[3, 9, 27, 30]); + let witness = LCCSWitness::::new(&ccs_shape, &W)?; + + let up = P::setup(witness.W.num_vars, Some(witness.W.num_vars), &mut rng).unwrap(); + let (ck, _vk) = P::trim(&up, witness.W.num_vars, 0, None).unwrap(); + + let commitment_W = witness.commit::(&ck); + + let s1 = (NUM_CONSTRAINTS - 1).checked_ilog2().unwrap_or(0) + 1; + let rs: Vec = (0..s1).map(|_| F::rand(&mut rng)).collect(); + + let z = fold_vec_to_mle_low(&X, &vec_to_mle(&W)); + + let rows = NUM_CONSTRAINTS; + let columns = NUM_WITNESS + NUM_PUBLIC; + + let Mrs: Vec> = ark_std::cfg_iter!(ccs_shape.Ms) + .map(|M| matrix_to_mle(rows, columns, M).fix_variables(rs.as_slice())) + .collect(); + + let n = columns.next_power_of_two(); + let shift = usize::BITS - ((n - 1).checked_ilog2().unwrap_or(0) + 1); + + let vs: Vec = ark_std::cfg_iter!(Mrs) + .map(|M| { + (0..n) + .map(|y| *M.index(y.reverse_bits() >> shift) * z.index(y)) + .sum() + }) + .collect(); + + let instance = LCCSInstance::::new(&ccs_shape, &commitment_W, &X, &rs, &vs)?; + + ccs_shape.is_satisfied_linearized::(&instance, &witness, &ck)?; + + // Provide invalid witness. + let invalid_W = to_field_elements::(&[4, 9, 27, 30]); + let invalid_witness = LCCSWitness::::new(&ccs_shape, &invalid_W)?; + let commitment_invalid_W = invalid_witness.commit::(&ck); + + let instance = + LCCSInstance::::new(&ccs_shape, &commitment_invalid_W, &X, &rs, &vs)?; + assert_eq!( + ccs_shape.is_satisfied_linearized(&instance, &invalid_witness, &ck), + Err(Error::NotSatisfied) + ); + + // Provide invalid public input. + let invalid_X = to_field_elements::(&[1, 36]); + let instance = + LCCSInstance::::new(&ccs_shape, &commitment_W, &invalid_X, &rs, &vs)?; + assert_eq!( + ccs_shape.is_satisfied_linearized(&instance, &witness, &ck), + Err(Error::NotSatisfied) + ); + + Ok(()) + } } From 4431f1e7849ccc0354b9a113c570f01016e5e1d7 Mon Sep 17 00:00:00 2001 From: Samuel Judson Date: Thu, 15 Feb 2024 10:23:08 -0500 Subject: [PATCH 18/26] Product renaming Co-authored-by: Dan Dore --- nova/src/ccs/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/src/ccs/mod.rs b/nova/src/ccs/mod.rs index a0ac21a31..bef4b016e 100644 --- a/nova/src/ccs/mod.rs +++ b/nova/src/ccs/mod.rs @@ -85,7 +85,7 @@ impl CCSShape { let mut acc = vec![G::ScalarField::ZERO; self.num_constraints]; for (c, S) in &self.cSs { - let mut circle_product = vec![*c; self.num_constraints]; + let mut hadamard_product = vec![*c; self.num_constraints]; for idx in S { ark_std::cfg_iter_mut!(circle_product) From 76f3041fc524389a929ae8d2994bfe39ad549579 Mon Sep 17 00:00:00 2001 From: Samuel Judson Date: Thu, 15 Feb 2024 10:14:21 -0500 Subject: [PATCH 19/26] Revert "Unify shapes." This reverts commit 3463e430c863f5711d698373c991c297ee3a3a17. --- nova/src/ccs/mod.rs | 372 +------------------------------------------- 1 file changed, 2 insertions(+), 370 deletions(-) diff --git a/nova/src/ccs/mod.rs b/nova/src/ccs/mod.rs index bef4b016e..d9f6b7650 100644 --- a/nova/src/ccs/mod.rs +++ b/nova/src/ccs/mod.rs @@ -1,20 +1,13 @@ -use ark_crypto_primitives::sponge::CryptographicSponge; use ark_ec::{AdditiveGroup, CurveGroup}; use ark_ff::Field; -use ark_poly::{ - DenseMVPolynomial, DenseMultilinearExtension, MultilinearExtension, SparseMultilinearExtension, -}; -use ark_poly_commit::{LabeledPolynomial, PolynomialCommitment}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_std::ops::Index; use ark_std::Zero; use std::ops::Neg; #[cfg(feature = "parallel")] use rayon::iter::{ - IndexedParallelIterator, IntoParallelIterator, IntoParallelRefIterator, - IntoParallelRefMutIterator, ParallelIterator, + IndexedParallelIterator, IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator, }; use super::commitment::CommitmentScheme; @@ -22,16 +15,13 @@ use super::commitment::CommitmentScheme; use super::r1cs::R1CSShape; pub use super::sparse::{MatrixRef, SparseMatrix}; +pub mod lccs; pub mod mle; -use mle::{fold_vec_to_mle_low, matrix_to_mle, mle_to_mvp, vec_to_mle}; #[derive(Debug, Copy, Clone, PartialEq)] pub enum Error { InvalidWitnessLength, InvalidInputLength, - InvalidEvaluationPoint, - InvalidTargets, - FailedWitnessCommitting, NotSatisfied, } @@ -108,65 +98,6 @@ impl CCSShape { Ok(()) } - - pub fn is_satisfied_linearized< - M: DenseMVPolynomial, - S: CryptographicSponge, - P: PolynomialCommitment, - >( - &self, - U: &LCCSInstance, - W: &LCCSWitness, - ck: &P::CommitterKey, - ) -> Result<(), Error> - where - P::Commitment: PartialEq, - { - assert_eq!(U.X.len(), self.num_io); - - let z: DenseMultilinearExtension = fold_vec_to_mle_low(&U.X, &W.W); - - let rows = self.num_constraints; - let columns = self.num_io + self.num_vars; - - let Mrs: Vec> = ark_std::cfg_iter!(&self.Ms) - .map(|M| matrix_to_mle(rows, columns, M).fix_variables(U.rs.as_slice())) - .collect(); - - let n = columns.next_power_of_two(); - let shift = usize::BITS - ((n - 1).checked_ilog2().unwrap_or(0) + 1); // for fixing endianness - - let Mzs: Vec = ark_std::cfg_iter!(Mrs) - .map(|M| { - (0..n) - .map(|y| *M.index(y.reverse_bits() >> shift) * z.index(y)) - .sum() - }) - .collect(); - - if ark_std::cfg_into_iter!(0..self.num_matrices).any(|idx| Mzs[idx] != U.vs[idx]) { - return Err(Error::NotSatisfied); - } - - let mvp_W: M = mle_to_mvp::(&W.W); - - let lab_W = LabeledPolynomial::::new( - "witness".to_string(), - mvp_W, - Some(W.W.num_vars), - None, - ); - - if let Ok(commit) = P::commit(ck, &[lab_W], None) { - if U.commitment_W != *commit.0[0].commitment() { - return Err(Error::NotSatisfied); - } - - Ok(()) - } else { - Err(Error::FailedWitnessCommitting) - } - } } /// Create an object of type `CCSShape` from the specified R1CS shape @@ -263,149 +194,6 @@ impl> CCSInstance { } } -/// A type that holds a witness for a given LCCS instance. -#[derive(Clone, Debug, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)] -pub struct LCCSWitness { - pub W: DenseMultilinearExtension, -} - -/// A type that holds an LCCS instance. -#[derive(CanonicalSerialize, CanonicalDeserialize)] -pub struct LCCSInstance< - G: CurveGroup, - M: DenseMVPolynomial, - S: CryptographicSponge, - P: PolynomialCommitment, -> { - /// Commitment to MLE of witness. - /// - /// C in HyperNova/CCS papers. - pub commitment_W: P::Commitment, - /// X is assumed to start with a `ScalarField` field element `u`. - pub X: Vec, - /// (Random) evaluation point - pub rs: Vec, - /// Evaluation targets - pub vs: Vec, -} - -impl< - G: CurveGroup, - M: DenseMVPolynomial, - S: CryptographicSponge, - P: PolynomialCommitment, - > Clone for LCCSInstance -{ - fn clone(&self) -> Self { - Self { - commitment_W: self.commitment_W.clone(), - X: self.X.clone(), - rs: self.rs.clone(), - vs: self.vs.clone(), - } - } -} - -impl< - G: CurveGroup, - M: DenseMVPolynomial, - S: CryptographicSponge, - P: PolynomialCommitment, - > PartialEq for LCCSInstance -where - P::Commitment: PartialEq, -{ - fn eq(&self, other: &Self) -> bool - where - P::Commitment: PartialEq, - { - self.commitment_W == other.commitment_W && self.X == other.X - } -} - -impl< - G: CurveGroup, - M: DenseMVPolynomial, - S: CryptographicSponge, - P: PolynomialCommitment, - > Eq for LCCSInstance -where - P::Commitment: Eq, -{ -} - -impl LCCSWitness { - /// A method to create a witness object using a vector of scalars. - pub fn new(shape: &CCSShape, W: &[G::ScalarField]) -> Result { - if shape.num_vars != W.len() { - Err(Error::InvalidWitnessLength) - } else { - Ok(Self { W: vec_to_mle(W) }) - } - } - - pub fn zero(shape: &CCSShape) -> Self { - Self { - W: vec_to_mle(vec![G::ScalarField::ZERO; shape.num_vars].as_slice()), - } - } - - /// Commits to the witness using the supplied key - pub fn commit< - M: DenseMVPolynomial, - S: CryptographicSponge, - P: PolynomialCommitment, - >( - &self, - ck: &P::CommitterKey, - ) -> P::Commitment { - let mvp_W: M = mle_to_mvp::(&self.W); - - let lab_W = LabeledPolynomial::::new( - "witness".to_string(), - mvp_W, - Some(self.W.num_vars), - None, - ); - - let wc = P::commit(ck, &[lab_W], None).unwrap(); - wc.0[0].commitment().clone() - } -} - -impl< - G: CurveGroup, - M: DenseMVPolynomial, - S: CryptographicSponge, - P: PolynomialCommitment, - > LCCSInstance -{ - /// A method to create an instance object using constituent elements. - pub fn new( - shape: &CCSShape, - commitment_W: &P::Commitment, - X: &[G::ScalarField], - rs: &[G::ScalarField], - vs: &[G::ScalarField], - ) -> Result { - if X.is_empty() || shape.num_io != X.len() { - Err(Error::InvalidInputLength) - } else if ((shape.num_constraints - 1).checked_ilog2().unwrap_or(0) + 1) != rs.len() as u32 - { - Err(Error::InvalidEvaluationPoint) - } else if shape.num_matrices != vs.len() { - Err(Error::InvalidTargets) - } else { - Ok(Self { - commitment_W: commitment_W.clone(), - X: X.to_owned(), - rs: rs.to_owned(), - vs: vs.to_owned(), - }) - } - } -} - #[cfg(test)] mod tests { #![allow(non_upper_case_globals)] @@ -510,160 +298,4 @@ mod tests { ); Ok(()) } - - use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; - use ark_poly::polynomial::multivariate::{SparsePolynomial, SparseTerm}; - use ark_poly_commit::marlin_pst13_pc::MarlinPST13; - use ark_std::UniformRand; - - use ark_test_curves::bls12_381::Bls12_381; - - type F = as ark_ec::PrimeGroup>::ScalarField; - - type M = SparsePolynomial; // confusingly, the DenseMVPolynomial trait is only implemented by SparsePolynomial - type S = PoseidonSponge; - type P = MarlinPST13; - - #[test] - fn zero_instance_is_satisfied_linearized() -> Result<(), Error> { - #[rustfmt::skip] - let a = { - let a: &[&[u64]] = &[ - &[1, 2, 3], - &[3, 4, 5], - &[6, 7, 8], - ]; - to_field_sparse::(a) - }; - - const NUM_CONSTRAINTS: usize = 3; - const NUM_WITNESS: usize = 1; - const NUM_PUBLIC: usize = 2; - - let mut rng = ark_std::test_rng(); - - let r1cs_shape: R1CSShape = - R1CSShape::::new(NUM_CONSTRAINTS, NUM_WITNESS, NUM_PUBLIC, &a, &a, &a).unwrap(); - - let ccs_shape = CCSShape::from(r1cs_shape); - - let X = to_field_elements::(&[0, 0]); - let W = to_field_elements::(&[0]); - let witness = LCCSWitness::::new(&ccs_shape, &W)?; - - let up = P::setup(witness.W.num_vars, Some(witness.W.num_vars), &mut rng).unwrap(); - let (ck, _vk) = P::trim(&up, witness.W.num_vars, 0, None).unwrap(); - - let commitment_W = witness.commit::(&ck); - - let s1 = (NUM_CONSTRAINTS - 1).checked_ilog2().unwrap_or(0) + 1; - let rs: Vec = (0..s1).map(|_| F::rand(&mut rng)).collect(); - - let z = fold_vec_to_mle_low(&X, &vec_to_mle(&W)); - - let rows = NUM_CONSTRAINTS; - let columns = NUM_WITNESS + NUM_PUBLIC; - - let Mrs: Vec> = ark_std::cfg_iter!(ccs_shape.Ms) - .map(|M| matrix_to_mle(rows, columns, M).fix_variables(rs.as_slice())) - .collect(); - - let n = columns.next_power_of_two(); - let shift = usize::BITS - ((n - 1).checked_ilog2().unwrap_or(0) + 1); - - let vs: Vec = ark_std::cfg_iter!(Mrs) - .map(|M| { - (0..n) - .map(|y| *M.index(y.reverse_bits() >> shift) * z.index(y)) - .sum() - }) - .collect(); - - let instance = LCCSInstance::::new(&ccs_shape, &commitment_W, &X, &rs, &vs)?; - - ccs_shape.is_satisfied_linearized::(&instance, &witness, &ck)?; - - Ok(()) - } - - #[test] - fn is_satisfied_linearized() -> Result<(), Error> { - let (a, b, c) = { - ( - to_field_sparse::(A), - to_field_sparse::(B), - to_field_sparse::(C), - ) - }; - - const NUM_CONSTRAINTS: usize = 4; - const NUM_WITNESS: usize = 4; - const NUM_PUBLIC: usize = 2; - - let mut rng = ark_std::test_rng(); - - let r1cs_shape: R1CSShape = - R1CSShape::::new(NUM_CONSTRAINTS, NUM_WITNESS, NUM_PUBLIC, &a, &b, &c).unwrap(); - - let ccs_shape = CCSShape::from(r1cs_shape); - - let X = to_field_elements::(&[1, 35]); - let W = to_field_elements::(&[3, 9, 27, 30]); - let witness = LCCSWitness::::new(&ccs_shape, &W)?; - - let up = P::setup(witness.W.num_vars, Some(witness.W.num_vars), &mut rng).unwrap(); - let (ck, _vk) = P::trim(&up, witness.W.num_vars, 0, None).unwrap(); - - let commitment_W = witness.commit::(&ck); - - let s1 = (NUM_CONSTRAINTS - 1).checked_ilog2().unwrap_or(0) + 1; - let rs: Vec = (0..s1).map(|_| F::rand(&mut rng)).collect(); - - let z = fold_vec_to_mle_low(&X, &vec_to_mle(&W)); - - let rows = NUM_CONSTRAINTS; - let columns = NUM_WITNESS + NUM_PUBLIC; - - let Mrs: Vec> = ark_std::cfg_iter!(ccs_shape.Ms) - .map(|M| matrix_to_mle(rows, columns, M).fix_variables(rs.as_slice())) - .collect(); - - let n = columns.next_power_of_two(); - let shift = usize::BITS - ((n - 1).checked_ilog2().unwrap_or(0) + 1); - - let vs: Vec = ark_std::cfg_iter!(Mrs) - .map(|M| { - (0..n) - .map(|y| *M.index(y.reverse_bits() >> shift) * z.index(y)) - .sum() - }) - .collect(); - - let instance = LCCSInstance::::new(&ccs_shape, &commitment_W, &X, &rs, &vs)?; - - ccs_shape.is_satisfied_linearized::(&instance, &witness, &ck)?; - - // Provide invalid witness. - let invalid_W = to_field_elements::(&[4, 9, 27, 30]); - let invalid_witness = LCCSWitness::::new(&ccs_shape, &invalid_W)?; - let commitment_invalid_W = invalid_witness.commit::(&ck); - - let instance = - LCCSInstance::::new(&ccs_shape, &commitment_invalid_W, &X, &rs, &vs)?; - assert_eq!( - ccs_shape.is_satisfied_linearized(&instance, &invalid_witness, &ck), - Err(Error::NotSatisfied) - ); - - // Provide invalid public input. - let invalid_X = to_field_elements::(&[1, 36]); - let instance = - LCCSInstance::::new(&ccs_shape, &commitment_W, &invalid_X, &rs, &vs)?; - assert_eq!( - ccs_shape.is_satisfied_linearized(&instance, &witness, &ck), - Err(Error::NotSatisfied) - ); - - Ok(()) - } } From 6d046c1de531f25683c11fcc428888064759b637 Mon Sep 17 00:00:00 2001 From: Samuel Judson Date: Thu, 15 Feb 2024 17:05:21 -0500 Subject: [PATCH 20/26] Move to polynomial/poly commitment implementations from Spartan repo. --- nova/src/ccs/lccs.rs | 242 ++++++++++++------------------------ nova/src/ccs/mle.rs | 242 +++++++++++++----------------------- nova/src/ccs/mod.rs | 4 +- spartan/src/dense_mlpoly.rs | 3 +- spartan/src/lib.rs | 2 +- 5 files changed, 172 insertions(+), 321 deletions(-) diff --git a/nova/src/ccs/lccs.rs b/nova/src/ccs/lccs.rs index 0e853f7d0..60c3656a3 100644 --- a/nova/src/ccs/lccs.rs +++ b/nova/src/ccs/lccs.rs @@ -1,13 +1,12 @@ -use ark_crypto_primitives::sponge::CryptographicSponge; use ark_ec::{AdditiveGroup, CurveGroup}; use ark_ff::Field; -use ark_poly::{ - DenseMVPolynomial, DenseMultilinearExtension, MultilinearExtension, SparseMultilinearExtension, -}; -use ark_poly_commit::{LabeledPolynomial, PolynomialCommitment}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_spartan::{ + dense_mlpoly::DensePolynomial as DenseMultilinearExtension, + polycommitments::PolyCommitmentScheme, + sparse_mlpoly::SparsePolynomial as SparseMultilinearExtension, +}; -use ark_std::ops::Index; use std::ops::Neg; #[cfg(feature = "parallel")] @@ -15,7 +14,7 @@ use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterato use super::super::r1cs::R1CSShape; pub use super::super::sparse::{MatrixRef, SparseMatrix}; -use super::mle::{fold_vec_to_mle_low, matrix_to_mle, mle_to_mvp, vec_to_mle}; +use super::mle::{compose_mle_input, matrix_to_mle, vec_to_mle}; #[derive(Debug, Copy, Clone, PartialEq)] pub enum Error { @@ -23,7 +22,6 @@ pub enum Error { InvalidInputLength, InvalidEvaluationPoint, InvalidTargets, - FailedWitnessCommitting, NotSatisfied, } @@ -58,35 +56,26 @@ pub struct LCCSShape { } impl LCCSShape { - pub fn is_satisfied< - M: DenseMVPolynomial, - S: CryptographicSponge, - P: PolynomialCommitment, - >( + pub fn is_satisfied>( &self, - U: &LCCSInstance, + U: &LCCSInstance, W: &LCCSWitness, - ck: &P::CommitterKey, - ) -> Result<(), Error> - where - P::Commitment: PartialEq, - { + ck: &C::PolyCommitmentKey, + ) -> Result<(), Error> { assert_eq!(U.X.len(), self.num_io); - - let z: DenseMultilinearExtension = fold_vec_to_mle_low(&U.X, &W.W); - - let Mrs: Vec> = ark_std::cfg_iter!(&self.Ms) - .map(|M| M.fix_variables(U.rs.as_slice())) - .collect(); + let X = vec_to_mle(U.X.as_slice()); + let z = DenseMultilinearExtension::::merge(&[X, W.W.to_owned()]); let n = (self.num_io + self.num_vars).next_power_of_two(); + let s = (n - 1).checked_ilog2().unwrap_or(0) + 1; - let shift = usize::BITS - ((n - 1).checked_ilog2().unwrap_or(0) + 1); // for fixing endianness - - let Mzs: Vec = ark_std::cfg_iter!(Mrs) + let Mzs: Vec = ark_std::cfg_iter!(&self.Ms) .map(|M| { (0..n) - .map(|y| *M.index(y.reverse_bits() >> shift) * z.index(y)) + .map(|y| { + let ry = compose_mle_input(U.rs.as_slice(), y, s as usize); + M.evaluate(ry.as_slice()) * z.evaluate::(&ry[U.rs.len()..]) + }) .sum() }) .collect(); @@ -95,24 +84,11 @@ impl LCCSShape { return Err(Error::NotSatisfied); } - let mvp_W: M = mle_to_mvp::(&W.W); - - let lab_W = LabeledPolynomial::::new( - "witness".to_string(), - mvp_W, - Some(W.W.num_vars), - None, - ); - - if let Ok(commit) = P::commit(ck, &[lab_W], None) { - if U.commitment_W != *commit.0[0].commitment() { - return Err(Error::NotSatisfied); - } - - Ok(()) - } else { - Err(Error::FailedWitnessCommitting) + if U.commitment_W != C::commit(&W.W, ck) { + return Err(Error::NotSatisfied); } + + Ok(()) } } @@ -150,16 +126,11 @@ pub struct LCCSWitness { /// A type that holds an LCCS instance. #[derive(CanonicalSerialize, CanonicalDeserialize)] -pub struct LCCSInstance< - G: CurveGroup, - M: DenseMVPolynomial, - S: CryptographicSponge, - P: PolynomialCommitment, -> { +pub struct LCCSInstance> { /// Commitment to MLE of witness. /// /// C in HyperNova/CCS papers. - pub commitment_W: P::Commitment, + pub commitment_W: C::Commitment, /// X is assumed to start with a `ScalarField` field element `u`. pub X: Vec, /// (Random) evaluation point @@ -168,13 +139,7 @@ pub struct LCCSInstance< pub vs: Vec, } -impl< - G: CurveGroup, - M: DenseMVPolynomial, - S: CryptographicSponge, - P: PolynomialCommitment, - > Clone for LCCSInstance -{ +impl> Clone for LCCSInstance { fn clone(&self) -> Self { Self { commitment_W: self.commitment_W.clone(), @@ -185,33 +150,13 @@ impl< } } -impl< - G: CurveGroup, - M: DenseMVPolynomial, - S: CryptographicSponge, - P: PolynomialCommitment, - > PartialEq for LCCSInstance -where - P::Commitment: PartialEq, -{ - fn eq(&self, other: &Self) -> bool - where - P::Commitment: PartialEq, - { +impl> PartialEq for LCCSInstance { + fn eq(&self, other: &Self) -> bool { self.commitment_W == other.commitment_W && self.X == other.X } } -impl< - G: CurveGroup, - M: DenseMVPolynomial, - S: CryptographicSponge, - P: PolynomialCommitment, - > Eq for LCCSInstance -where - P::Commitment: Eq, -{ -} +impl> Eq for LCCSInstance {} impl LCCSWitness { /// A method to create a witness object using a vector of scalars. @@ -230,39 +175,16 @@ impl LCCSWitness { } /// Commits to the witness using the supplied key - pub fn commit< - M: DenseMVPolynomial, - S: CryptographicSponge, - P: PolynomialCommitment, - >( - &self, - ck: &P::CommitterKey, - ) -> P::Commitment { - let mvp_W: M = mle_to_mvp::(&self.W); - - let lab_W = LabeledPolynomial::::new( - "witness".to_string(), - mvp_W, - Some(self.W.num_vars), - None, - ); - - let wc = P::commit(ck, &[lab_W], None).unwrap(); - wc.0[0].commitment().clone() + pub fn commit>(&self, ck: &C::PolyCommitmentKey) -> C::Commitment { + C::commit(&self.W, ck) } } -impl< - G: CurveGroup, - M: DenseMVPolynomial, - S: CryptographicSponge, - P: PolynomialCommitment, - > LCCSInstance -{ +impl> LCCSInstance { /// A method to create an instance object using constituent elements. pub fn new( shape: &LCCSShape, - commitment_W: &P::Commitment, + commitment_W: &C::Commitment, X: &[G::ScalarField], rs: &[G::ScalarField], vs: &[G::ScalarField], @@ -292,21 +214,15 @@ mod tests { use super::*; - use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; - use ark_poly::polynomial::multivariate::{SparsePolynomial, SparseTerm}; - use ark_poly_commit::marlin_pst13_pc::MarlinPST13; - use ark_std::UniformRand; - - use ark_test_curves::bls12_381::{Bls12_381, G1Projective as G}; - - type F = as ark_ec::PrimeGroup>::ScalarField; - - type M = SparsePolynomial; // confusingly, the DenseMVPolynomial trait is only implemented by SparsePolynomial - type S = PoseidonSponge; - type P = MarlinPST13; + use ark_spartan::polycommitments::zeromorph::Zeromorph; + use ark_spartan::polycommitments::PCSKeys; + use ark_std::{test_rng, UniformRand}; + use ark_test_curves::bls12_381::{Bls12_381 as E, Fr, G1Projective as G}; use crate::r1cs::tests::{to_field_elements, to_field_sparse, A, B, C}; + type Z = Zeromorph; + #[test] fn zero_instance_is_satisfied() -> Result<(), Error> { #[rustfmt::skip] @@ -323,46 +239,43 @@ mod tests { const NUM_WITNESS: usize = 1; const NUM_PUBLIC: usize = 2; - let mut rng = ark_std::test_rng(); - let r1cs_shape: R1CSShape = R1CSShape::::new(NUM_CONSTRAINTS, NUM_WITNESS, NUM_PUBLIC, &a, &a, &a).unwrap(); let lccs_shape = LCCSShape::from(r1cs_shape); + let mut rng = test_rng(); + let SRS = Z::setup(4, b"test", &mut rng).unwrap(); + let PCSKeys { ck, .. } = Z::trim(&SRS, 4); + let X = to_field_elements::(&[0, 0]); let W = to_field_elements::(&[0]); let witness = LCCSWitness::::new(&lccs_shape, &W)?; - let up = P::setup(witness.W.num_vars, Some(witness.W.num_vars), &mut rng).unwrap(); - let (ck, _vk) = P::trim(&up, witness.W.num_vars, 0, None).unwrap(); - - let commitment_W = witness.commit::(&ck); + let commitment_W = witness.commit::(&ck); let s1 = (NUM_CONSTRAINTS - 1).checked_ilog2().unwrap_or(0) + 1; - let rs: Vec = (0..s1).map(|_| F::rand(&mut rng)).collect(); + let rs: Vec = (0..s1).map(|_| Fr::rand(&mut rng)).collect(); - let z = fold_vec_to_mle_low(&X, &vec_to_mle(&W)); - - let Mrs: Vec> = ark_std::cfg_iter!(lccs_shape.Ms) - .map(|M| M.fix_variables(rs.as_slice())) - .collect(); + let z = DenseMultilinearExtension::::merge(&[vec_to_mle(&X), vec_to_mle(&W)]); let n = (NUM_WITNESS + NUM_PUBLIC).next_power_of_two(); + let s2 = (n - 1).checked_ilog2().unwrap_or(0) + 1; - let shift = usize::BITS - ((n - 1).checked_ilog2().unwrap_or(0) + 1); - - let vs: Vec = ark_std::cfg_iter!(Mrs) + let vs: Vec = ark_std::cfg_iter!(&lccs_shape.Ms) .map(|M| { (0..n) - .map(|y| *M.index(y.reverse_bits() >> shift) * z.index(y)) + .map(|idx| { + let ry = compose_mle_input(rs.as_slice(), idx, s2 as usize); + M.evaluate(ry.as_slice()) * z.evaluate::(&ry[rs.len()..]) + }) .sum() }) .collect(); - let instance = LCCSInstance::::new(&lccs_shape, &commitment_W, &X, &rs, &vs)?; + let instance = LCCSInstance::::new(&lccs_shape, &commitment_W, &X, &rs, &vs)?; - lccs_shape.is_satisfied::(&instance, &witness, &ck)?; + lccs_shape.is_satisfied::(&instance, &witness, &ck)?; Ok(()) } @@ -381,54 +294,58 @@ mod tests { const NUM_WITNESS: usize = 4; const NUM_PUBLIC: usize = 2; - let mut rng = ark_std::test_rng(); - let r1cs_shape: R1CSShape = R1CSShape::::new(NUM_CONSTRAINTS, NUM_WITNESS, NUM_PUBLIC, &a, &b, &c).unwrap(); let lccs_shape = LCCSShape::from(r1cs_shape); + let mut rng = test_rng(); + let SRS = Z::setup(8, b"test", &mut rng).unwrap(); + let PCSKeys { ck, .. } = Z::trim(&SRS, 8); + let X = to_field_elements::(&[1, 35]); let W = to_field_elements::(&[3, 9, 27, 30]); let witness = LCCSWitness::::new(&lccs_shape, &W)?; - let up = P::setup(witness.W.num_vars, Some(witness.W.num_vars), &mut rng).unwrap(); - let (ck, _vk) = P::trim(&up, witness.W.num_vars, 0, None).unwrap(); - - let commitment_W = witness.commit::(&ck); + let commitment_W = witness.commit::(&ck); let s1 = (NUM_CONSTRAINTS - 1).checked_ilog2().unwrap_or(0) + 1; - let rs: Vec = (0..s1).map(|_| F::rand(&mut rng)).collect(); - - let z = fold_vec_to_mle_low(&X, &vec_to_mle(&W)); + let rs: Vec = (0..s1).map(|_| Fr::rand(&mut rng)).collect(); - let Mrs: Vec> = ark_std::cfg_iter!(lccs_shape.Ms) - .map(|M| M.fix_variables(rs.as_slice())) - .collect(); + let z = DenseMultilinearExtension::::merge(&[vec_to_mle(&X), vec_to_mle(&W)]); let n = (NUM_WITNESS + NUM_PUBLIC).next_power_of_two(); + let s2 = (n - 1).checked_ilog2().unwrap_or(0) + 1; - let shift = usize::BITS - ((n - 1).checked_ilog2().unwrap_or(0) + 1); - - let vs: Vec = ark_std::cfg_iter!(Mrs) + let vs: Vec = ark_std::cfg_iter!(&lccs_shape.Ms) .map(|M| { (0..n) - .map(|y| *M.index(y.reverse_bits() >> shift) * z.index(y)) + .map(|y| { + let ry = compose_mle_input(rs.as_slice(), y, s2 as usize); + M.evaluate(ry.as_slice()) * z.evaluate::(&ry[rs.len()..]) + }) .sum() }) .collect(); - let instance = LCCSInstance::::new(&lccs_shape, &commitment_W, &X, &rs, &vs)?; + let instance = LCCSInstance::::new(&lccs_shape, &commitment_W, &X, &rs, &vs)?; + + lccs_shape.is_satisfied::(&instance, &witness, &ck)?; - lccs_shape.is_satisfied::(&instance, &witness, &ck)?; + // Change commitment. + let invalid_commitment = commitment_W + commitment_W; + let instance = LCCSInstance::::new(&lccs_shape, &invalid_commitment, &X, &rs, &vs)?; + assert_eq!( + lccs_shape.is_satisfied(&instance, &witness, &ck), + Err(Error::NotSatisfied) + ); // Provide invalid witness. let invalid_W = to_field_elements::(&[4, 9, 27, 30]); let invalid_witness = LCCSWitness::::new(&lccs_shape, &invalid_W)?; - let commitment_invalid_W = invalid_witness.commit::(&ck); + let commitment_invalid_W = invalid_witness.commit::(&ck); - let instance = - LCCSInstance::::new(&lccs_shape, &commitment_invalid_W, &X, &rs, &vs)?; + let instance = LCCSInstance::::new(&lccs_shape, &commitment_invalid_W, &X, &rs, &vs)?; assert_eq!( lccs_shape.is_satisfied(&instance, &invalid_witness, &ck), Err(Error::NotSatisfied) @@ -436,8 +353,7 @@ mod tests { // Provide invalid public input. let invalid_X = to_field_elements::(&[1, 36]); - let instance = - LCCSInstance::::new(&lccs_shape, &commitment_W, &invalid_X, &rs, &vs)?; + let instance = LCCSInstance::::new(&lccs_shape, &commitment_W, &invalid_X, &rs, &vs)?; assert_eq!( lccs_shape.is_satisfied(&instance, &witness, &ck), Err(Error::NotSatisfied) diff --git a/nova/src/ccs/mle.rs b/nova/src/ccs/mle.rs index 399c148ec..f167bc35a 100644 --- a/nova/src/ccs/mle.rs +++ b/nova/src/ccs/mle.rs @@ -1,65 +1,13 @@ //! Helper code for multilinear extensions -use ark_ff::{Field, PrimeField}; -use ark_poly::{ - multivariate::Term, DenseMVPolynomial, DenseMultilinearExtension, MultilinearExtension, - SparseMultilinearExtension, +use ark_ff::PrimeField; +use ark_spartan::dense_mlpoly::DensePolynomial as DenseMultilinearExtension; +use ark_spartan::math::Math; +use ark_spartan::sparse_mlpoly::{ + SparsePolyEntry as MultilinearEvaluation, SparsePolynomial as SparseMultilinearExtension, }; use super::super::sparse::SparseMatrix; -use super::super::utils::iter_bits_le; - -/// Utility function for mle -> mvp conversion. -fn compute_coeffs_from_evals(pts: &[F]) -> Vec { - let n = pts.len(); - - // This code recursively implements sum over subsets to compute - // the terms of the polynomial from its defining evaluations. - // - // see: https://crypto.stackexchange.com/a/84416 - if n == 1 { - return pts.to_vec(); - } - - let h = n / 2; - let l = compute_coeffs_from_evals(&pts[0..h]); - let r = compute_coeffs_from_evals(&pts[h..n]); - - [ - l.clone(), - l.iter() - .zip(r.iter()) - .map(|(vl, vr)| *vr - vl) - .collect::>(), - ] - .concat() -} - -/// Converts an mle into a generic multivariate polynomial. -pub fn mle_to_mvp>(mle: &DenseMultilinearExtension) -> M { - let evals = mle.to_evaluations(); - let coeffs = compute_coeffs_from_evals(evals.as_slice()); - - let n = 1 << mle.num_vars; - - let terms: Vec<(F, M::Term)> = (0..n) - .map(|i| { - let bytes = (i as u32).to_le_bytes(); - let mut bits = iter_bits_le(&bytes); - - let mut t: Vec<(usize, usize)> = vec![]; - (0..mle.num_vars).for_each(|j| { - if bits.next().unwrap() { - t.push((j, 1)); - } - }); - - (coeffs[i], M::Term::new(t)) - }) - .collect(); - - M::from_coefficients_vec(mle.num_vars, terms) -} /// Converts a matrix into a (sparse) mle. pub fn matrix_to_mle( @@ -73,96 +21,51 @@ pub fn matrix_to_mle( let s1 = (m - 1).checked_ilog2().unwrap_or(0) + 1; let s2 = (n - 1).checked_ilog2().unwrap_or(0) + 1; - // number of columns in padded matrix - let n = if n == 1 { 2 } else { n.next_power_of_two() }; + let n = n.next_power_of_two(); - let evaluations: Vec<(usize, F)> = M.iter().map(|(i, j, value)| ((i * n + j), value)).collect(); + let evaluations: Vec> = M + .iter() + .map(|(i, j, value)| MultilinearEvaluation::new(i * n + j, value)) + .collect(); - SparseMultilinearExtension::from_evaluations((s1 + s2) as usize, &evaluations) + SparseMultilinearExtension::::new((s1 + s2) as usize, evaluations) } /// Converts a vector into a (dense) mle. -pub fn vec_to_mle(z: &[F]) -> DenseMultilinearExtension { +pub fn vec_to_mle(z: &[F]) -> DenseMultilinearExtension { let n = z.len(); assert!(n > 0); let mut z = z.to_owned(); - z.resize(if n == 1 { 2 } else { n.next_power_of_two() }, F::zero()); - - // compute s' - let s = (n - 1).checked_ilog2().unwrap_or(0) + 1; + z.resize(n.next_power_of_two(), F::zero()); - DenseMultilinearExtension::from_evaluations_vec(s as usize, z) + DenseMultilinearExtension::::new(z) } -/// Folds a vector into an mle representing another, as the lower-order entries (i.e., given vector `z` and `mle` encoding a vector `y`, returns an MLE encoding `z || y`). -pub fn fold_vec_to_mle_low( - z: &[F], - mle: &DenseMultilinearExtension, -) -> DenseMultilinearExtension { - let mut n = z.len(); - assert!(n > 0); - - let mut z = z.to_owned(); - z.extend(&mle.to_evaluations()); - - n = z.len(); - z.resize(if n == 1 { 2 } else { n.next_power_of_two() }, F::zero()); - - // compute s' - let s = (n - 1).checked_ilog2().unwrap_or(0) + 1; +pub fn compose_mle_input(r: &[F], y: usize, t: usize) -> Vec { + assert!(t < 32); - DenseMultilinearExtension::from_evaluations_vec(s as usize, z) + [ + r, + &y.get_bits(t) + .iter() + .map(|b| F::from(*b as u32)) + .collect::>(), + ] + .concat() + .to_vec() } #[cfg(test)] mod tests { use super::*; - use ark_poly::polynomial::multivariate::{SparsePolynomial, SparseTerm}; - use ark_poly::Polynomial; use ark_std::{UniformRand, Zero}; use ark_test_curves::bls12_381::{Fr, G1Projective as G}; use crate::r1cs::tests::to_field_sparse; use crate::utils::iter_bits_le; - #[test] - fn test_coeffs_from_evals() { - // evaluations vector: [ 10, 32, 57, 81 ] - // encoded multilinear polynomial by evaluations: 10(1-x)(1-y) + 32x(1-y) + 57(1-x)y + 81xy - // ... multiply out and collect terms... - // encoded multilinear polynomial by coefficients: 10 + 22x + 47y + 2xy - // - // from: https://crypto.stackexchange.com/a/84416 - let pts = [Fr::from(10), Fr::from(32), Fr::from(57), Fr::from(81)]; - let exp = [Fr::from(10), Fr::from(22), Fr::from(47), Fr::from(2)]; - - let coeffs = compute_coeffs_from_evals(&pts); - - assert_eq!(exp.len(), coeffs.len()); - assert!(exp.iter().zip(coeffs.iter()).all(|(e, c)| e == c)); - } - - #[test] - fn test_mle_to_mvp() { - let pts = [Fr::from(10), Fr::from(32), Fr::from(57), Fr::from(81)]; - let mle = DenseMultilinearExtension::::from_evaluations_slice(2, &pts); - let mvp: SparsePolynomial = mle_to_mvp(&mle); - - let exp = vec![ - (Fr::from(10), SparseTerm::new(vec![])), - (Fr::from(47), SparseTerm::new(vec![(1, 1)])), // mvp repr reorders internally - (Fr::from(22), SparseTerm::new(vec![(0, 1)])), - (Fr::from(2), SparseTerm::new(vec![(0, 1), (1, 1)])), - ]; - - let terms = mvp.terms(); - - assert_eq!(exp.len(), terms.len()); - assert!(exp.iter().zip(terms.iter()).all(|(e, t)| e == t)); - } - #[test] fn test_matrix_to_mle() { const NUM_ROWS: usize = 3; @@ -179,8 +82,6 @@ mod tests { let sparse_m = SparseMatrix::new(&to_field_sparse::(M), NUM_ROWS, NUM_COLS); let mle = matrix_to_mle(NUM_ROWS, NUM_COLS, &sparse_m); - assert_eq!(mle.num_vars, NUM_VARS); - let m = NUM_ROWS.next_power_of_two(); let n = NUM_COLS.next_power_of_two(); @@ -190,11 +91,12 @@ mod tests { let _j = j | row_mask; let j_bytes = _j.to_le_bytes(); - let j_bits: Vec = iter_bits_le(j_bytes.as_slice()) + let mut j_bits: Vec = iter_bits_le(j_bytes.as_slice()) .map(Fr::from) .take(NUM_VARS) .collect(); + j_bits.reverse(); let eval = mle.evaluate(&j_bits); let expected = if i < NUM_ROWS && j < NUM_COLS { @@ -219,12 +121,13 @@ mod tests { let n = LEN.next_power_of_two(); for (i, entry) in z.iter().enumerate().take(n) { let i_bytes = i.to_le_bytes(); - let i_bits: Vec = iter_bits_le(i_bytes.as_slice()) + let mut i_bits: Vec = iter_bits_le(i_bytes.as_slice()) .map(Fr::from) .take(NUM_VARS) - .collect(); + .collect::>(); - let eval = mle.evaluate(&i_bits); + i_bits.reverse(); + let eval = mle.evaluate::(&i_bits); let expected = if i < LEN { *entry } else { Fr::zero() }; assert_eq!(eval, expected); @@ -232,36 +135,67 @@ mod tests { } #[test] - fn test_fold_vec_to_mle_low() { - const VEC_LEN: usize = 30; - const MLE_LEN: usize = 60; - const TOT_LEN: usize = 90; // 60 + 30 + fn test_compose_mle_input() { + let rs1 = [Fr::from(1), Fr::from(4), Fr::from(6)]; + let ry1 = compose_mle_input(&rs1, 5, 3); // ...00101 -> 1, 0, 1 + + let ex1 = vec![ + Fr::from(1), + Fr::from(4), + Fr::from(6), + Fr::from(1), + Fr::from(0), + Fr::from(1), + ]; - const NUM_VARS: usize = 7; // 7 bits to represent each index in combination mle (30 + 60 < 128 = 2^7) + assert_eq!(ry1.len(), ex1.len()); + ex1.iter() + .zip(ry1.iter()) + .for_each(|(a, b)| assert_eq!(a, b)); + + let rs2: [Fr; 0] = []; + let ry2 = compose_mle_input(&rs2, 5, 3); // ...00101 -> 1, 0, 1 + + let ex2 = vec![Fr::from(1), Fr::from(0), Fr::from(1)]; + + assert_eq!(ry2.len(), ex2.len()); + ex2.iter() + .zip(ry2.iter()) + .for_each(|(a, b)| assert_eq!(a, b)); + + let rs3 = rs1; + let ry3 = compose_mle_input(&rs3, 5, 5); // ...00101 -> 0, 0, 1, 0, 1 + + let ex3 = vec![ + Fr::from(1), + Fr::from(4), + Fr::from(6), + Fr::from(0), + Fr::from(0), + Fr::from(1), + Fr::from(0), + Fr::from(1), + ]; - let mut rng = ark_std::test_rng(); - let z1: Vec = (0..MLE_LEN).map(|_| Fr::rand(&mut rng)).collect(); - let z2: Vec = (0..VEC_LEN).map(|_| Fr::rand(&mut rng)).collect(); - let mle1 = vec_to_mle(&z1); - let mle2 = fold_vec_to_mle_low(&z2, &mle1); + assert_eq!(ry3.len(), ex3.len()); + ex3.iter() + .zip(ry3.iter()) + .for_each(|(a, b)| assert_eq!(a, b)); - let n = TOT_LEN; - for i in 0..n { - let i_bytes = i.to_le_bytes(); - let i_bits: Vec = iter_bits_le(i_bytes.as_slice()) - .map(Fr::from) - .take(NUM_VARS) - .collect(); + let rs4 = rs1; + let ry4 = compose_mle_input(&rs4, 5, 2); // ...00101 -> 0, 1 - let eval = mle2.evaluate(&i_bits); + let ex4 = vec![ + Fr::from(1), + Fr::from(4), + Fr::from(6), + Fr::from(0), + Fr::from(1), + ]; - if (0..VEC_LEN).contains(&i) { - assert_eq!(eval, z2[i]); - } else if (VEC_LEN..TOT_LEN).contains(&i) { - assert_eq!(eval, z1[i - VEC_LEN]); - } else { - assert_eq!(eval, Fr::zero()); - } - } + assert_eq!(ry4.len(), ex4.len()); + ex4.iter() + .zip(ry4.iter()) + .for_each(|(a, b)| assert_eq!(a, b)); } } diff --git a/nova/src/ccs/mod.rs b/nova/src/ccs/mod.rs index d9f6b7650..35522b5cd 100644 --- a/nova/src/ccs/mod.rs +++ b/nova/src/ccs/mod.rs @@ -78,14 +78,14 @@ impl CCSShape { let mut hadamard_product = vec![*c; self.num_constraints]; for idx in S { - ark_std::cfg_iter_mut!(circle_product) + ark_std::cfg_iter_mut!(hadamard_product) .enumerate() .for_each(|(j, x)| *x *= Mzs[*idx][j]); } ark_std::cfg_iter_mut!(acc) .enumerate() - .for_each(|(i, s)| *s += circle_product[i]); + .for_each(|(i, s)| *s += hadamard_product[i]); } if ark_std::cfg_iter!(acc).any(|s| !s.is_zero()) { diff --git a/spartan/src/dense_mlpoly.rs b/spartan/src/dense_mlpoly.rs index 1395c2a94..d775aff15 100644 --- a/spartan/src/dense_mlpoly.rs +++ b/spartan/src/dense_mlpoly.rs @@ -16,7 +16,7 @@ use merlin::Transcript; #[cfg(feature = "parallel")] use rayon::prelude::*; -#[derive(Debug, Clone, CanonicalSerialize, CanonicalDeserialize)] +#[derive(Debug, Clone, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)] pub struct DensePolynomial where F: Sync + CanonicalSerialize + CanonicalDeserialize, @@ -25,6 +25,7 @@ where len: usize, Z: Vec, // evaluations of the polynomial in all the 2^num_vars Boolean inputs } + #[derive(Debug, Clone, CanonicalSerialize, CanonicalDeserialize)] pub struct PolyCommitmentGens where diff --git a/spartan/src/lib.rs b/spartan/src/lib.rs index 78d0947a3..458003ee4 100644 --- a/spartan/src/lib.rs +++ b/spartan/src/lib.rs @@ -21,7 +21,7 @@ mod product_tree; pub mod r1csinstance; mod r1csproof; mod random; -mod sparse_mlpoly; +pub mod sparse_mlpoly; mod sumcheck; mod timer; mod transcript; From f5c2001fc9c389a602d95a7e03978ba2ceda01c2 Mon Sep 17 00:00:00 2001 From: Samuel Judson Date: Thu, 15 Feb 2024 22:22:49 -0500 Subject: [PATCH 21/26] Move to unified matrix-based model. --- nova/src/ccs/mle.rs | 96 ++-------------- nova/src/ccs/mod.rs | 264 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 258 insertions(+), 102 deletions(-) diff --git a/nova/src/ccs/mle.rs b/nova/src/ccs/mle.rs index f167bc35a..e2b381612 100644 --- a/nova/src/ccs/mle.rs +++ b/nova/src/ccs/mle.rs @@ -2,7 +2,6 @@ use ark_ff::PrimeField; use ark_spartan::dense_mlpoly::DensePolynomial as DenseMultilinearExtension; -use ark_spartan::math::Math; use ark_spartan::sparse_mlpoly::{ SparsePolyEntry as MultilinearEvaluation, SparsePolynomial as SparseMultilinearExtension, }; @@ -21,11 +20,11 @@ pub fn matrix_to_mle( let s1 = (m - 1).checked_ilog2().unwrap_or(0) + 1; let s2 = (n - 1).checked_ilog2().unwrap_or(0) + 1; - let n = n.next_power_of_two(); + let s = 1 << s1; let evaluations: Vec> = M .iter() - .map(|(i, j, value)| MultilinearEvaluation::new(i * n + j, value)) + .map(|(i, j, value)| MultilinearEvaluation::new(j * s + i, value)) .collect(); SparseMultilinearExtension::::new((s1 + s2) as usize, evaluations) @@ -42,20 +41,6 @@ pub fn vec_to_mle(z: &[F]) -> DenseMultilinearExtension { DenseMultilinearExtension::::new(z) } -pub fn compose_mle_input(r: &[F], y: usize, t: usize) -> Vec { - assert!(t < 32); - - [ - r, - &y.get_bits(t) - .iter() - .map(|b| F::from(*b as u32)) - .collect::>(), - ] - .concat() - .to_vec() -} - #[cfg(test)] mod tests { use super::*; @@ -87,17 +72,17 @@ mod tests { for (i, row) in M.iter().enumerate().take(m) { for (j, entry) in row.iter().enumerate().take(n) { - let row_mask = (1 << 3) * i; // shift column bits - let _j = j | row_mask; + let col_mask = (1 << 2) * j; // shift column bits + let _i = i | col_mask; - let j_bytes = _j.to_le_bytes(); - let mut j_bits: Vec = iter_bits_le(j_bytes.as_slice()) + let i_bytes = _i.to_le_bytes(); + let mut i_bits: Vec = iter_bits_le(i_bytes.as_slice()) .map(Fr::from) .take(NUM_VARS) .collect(); - j_bits.reverse(); - let eval = mle.evaluate(&j_bits); + i_bits.reverse(); + let eval = mle.evaluate(&i_bits); let expected = if i < NUM_ROWS && j < NUM_COLS { (*entry).into() @@ -133,69 +118,4 @@ mod tests { assert_eq!(eval, expected); } } - - #[test] - fn test_compose_mle_input() { - let rs1 = [Fr::from(1), Fr::from(4), Fr::from(6)]; - let ry1 = compose_mle_input(&rs1, 5, 3); // ...00101 -> 1, 0, 1 - - let ex1 = vec![ - Fr::from(1), - Fr::from(4), - Fr::from(6), - Fr::from(1), - Fr::from(0), - Fr::from(1), - ]; - - assert_eq!(ry1.len(), ex1.len()); - ex1.iter() - .zip(ry1.iter()) - .for_each(|(a, b)| assert_eq!(a, b)); - - let rs2: [Fr; 0] = []; - let ry2 = compose_mle_input(&rs2, 5, 3); // ...00101 -> 1, 0, 1 - - let ex2 = vec![Fr::from(1), Fr::from(0), Fr::from(1)]; - - assert_eq!(ry2.len(), ex2.len()); - ex2.iter() - .zip(ry2.iter()) - .for_each(|(a, b)| assert_eq!(a, b)); - - let rs3 = rs1; - let ry3 = compose_mle_input(&rs3, 5, 5); // ...00101 -> 0, 0, 1, 0, 1 - - let ex3 = vec![ - Fr::from(1), - Fr::from(4), - Fr::from(6), - Fr::from(0), - Fr::from(0), - Fr::from(1), - Fr::from(0), - Fr::from(1), - ]; - - assert_eq!(ry3.len(), ex3.len()); - ex3.iter() - .zip(ry3.iter()) - .for_each(|(a, b)| assert_eq!(a, b)); - - let rs4 = rs1; - let ry4 = compose_mle_input(&rs4, 5, 2); // ...00101 -> 0, 1 - - let ex4 = vec![ - Fr::from(1), - Fr::from(4), - Fr::from(6), - Fr::from(0), - Fr::from(1), - ]; - - assert_eq!(ry4.len(), ex4.len()); - ex4.iter() - .zip(ry4.iter()) - .for_each(|(a, b)| assert_eq!(a, b)); - } } diff --git a/nova/src/ccs/mod.rs b/nova/src/ccs/mod.rs index 35522b5cd..012f30ce0 100644 --- a/nova/src/ccs/mod.rs +++ b/nova/src/ccs/mod.rs @@ -1,27 +1,31 @@ use ark_ec::{AdditiveGroup, CurveGroup}; use ark_ff::Field; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_spartan::polycommitments::PolyCommitmentScheme; use ark_std::Zero; use std::ops::Neg; #[cfg(feature = "parallel")] use rayon::iter::{ - IndexedParallelIterator, IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator, + IndexedParallelIterator, IntoParallelIterator, IntoParallelRefIterator, + IntoParallelRefMutIterator, ParallelIterator, }; use super::commitment::CommitmentScheme; use super::r1cs::R1CSShape; pub use super::sparse::{MatrixRef, SparseMatrix}; +use mle::vec_to_mle; -pub mod lccs; pub mod mle; #[derive(Debug, Copy, Clone, PartialEq)] pub enum Error { InvalidWitnessLength, InvalidInputLength, + InvalidEvaluationPoint, + InvalidTargets, NotSatisfied, } @@ -92,7 +96,32 @@ impl CCSShape { return Err(Error::NotSatisfied); } - if U.commitment_W != C::commit(pp, &W.W) { + if U.commitment_W != W.commit::(pp) { + return Err(Error::NotSatisfied); + } + + Ok(()) + } + + pub fn is_satisfied_linearized>( + &self, + U: &LCCSInstance, + W: &CCSWitness, + ck: &C::PolyCommitmentKey, + ) -> Result<(), Error> { + assert_eq!(W.W.len(), self.num_vars); + assert_eq!(U.X.len(), self.num_io); + + let z = [U.X.as_slice(), W.W.as_slice()].concat(); + let Mzs: Vec = ark_std::cfg_iter!(&self.Ms) + .map(|M| vec_to_mle(M.multiply_vec(&z).as_slice()).evaluate::(U.rs.as_slice())) + .collect(); + + if ark_std::cfg_into_iter!(0..self.num_matrices).any(|idx| Mzs[idx] != U.vs[idx]) { + return Err(Error::NotSatisfied); + } + + if U.commitment_W != W.commit_linearized::(ck) { return Err(Error::NotSatisfied); } @@ -126,7 +155,7 @@ pub struct CCSWitness { } /// A type that holds an CCS instance. -#[derive(CanonicalSerialize, CanonicalDeserialize)] +#[derive(Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] pub struct CCSInstance> { /// Commitment to witness. pub commitment_W: C::Commitment, @@ -143,14 +172,6 @@ impl> Clone for CCSInstance { } } -impl> PartialEq for CCSInstance { - fn eq(&self, other: &Self) -> bool { - self.commitment_W == other.commitment_W && self.X == other.X - } -} - -impl> Eq for CCSInstance where C::Commitment: Eq {} - impl CCSWitness { /// A method to create a witness object using a vector of scalars. pub fn new(shape: &CCSShape, W: &[G::ScalarField]) -> Result { @@ -171,6 +192,14 @@ impl CCSWitness { pub fn commit>(&self, pp: &C::PP) -> C::Commitment { C::commit(pp, &self.W) } + + /// Commits to the witness as a polynomial using the supplied key + fn commit_linearized>( + &self, + ck: &C::PolyCommitmentKey, + ) -> C::Commitment { + C::commit(&vec_to_mle(&self.W), ck) + } } impl> CCSInstance { @@ -194,6 +223,48 @@ impl> CCSInstance { } } +impl> LCCSInstance { + /// A method to create an instance object using constituent elements. + pub fn new( + shape: &CCSShape, + commitment_W: &C::Commitment, + X: &[G::ScalarField], + rs: &[G::ScalarField], + vs: &[G::ScalarField], + ) -> Result { + if X.is_empty() || shape.num_io != X.len() { + Err(Error::InvalidInputLength) + } else if ((shape.num_constraints - 1).checked_ilog2().unwrap_or(0) + 1) != rs.len() as u32 + { + Err(Error::InvalidEvaluationPoint) + } else if shape.num_matrices != vs.len() { + Err(Error::InvalidTargets) + } else { + Ok(Self { + commitment_W: commitment_W.clone(), + X: X.to_owned(), + rs: rs.to_owned(), + vs: vs.to_owned(), + }) + } + } +} + +/// A type that holds an LCCS instance. +#[derive(Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] +pub struct LCCSInstance> { + /// Commitment to MLE of witness. + /// + /// C in HyperNova/CCS papers. + pub commitment_W: C::Commitment, + /// X is assumed to start with a `ScalarField` field element `u`. + pub X: Vec, + /// (Random) evaluation point + pub rs: Vec, + /// Evaluation targets + pub vs: Vec, +} + #[cfg(test)] mod tests { #![allow(non_upper_case_globals)] @@ -201,11 +272,53 @@ mod tests { use super::*; use crate::pedersen::PedersenCommitment; - - use ark_test_curves::bls12_381::G1Projective as G; + use ark_test_curves::bls12_381::{Bls12_381 as E, Fr, G1Projective as G}; use crate::r1cs::tests::{to_field_elements, to_field_sparse, A, B, C}; + #[test] + fn test_r1cs_to_ccs() -> Result<(), Error> { + let (a, b, c) = { + ( + to_field_sparse::(A), + to_field_sparse::(B), + to_field_sparse::(C), + ) + }; + + const NUM_CONSTRAINTS: usize = 4; + const NUM_WITNESS: usize = 4; + const NUM_PUBLIC: usize = 2; + + let r1cs_shape: R1CSShape = + R1CSShape::::new(NUM_CONSTRAINTS, NUM_WITNESS, NUM_PUBLIC, &a, &b, &c).unwrap(); + + let ccs_shape = CCSShape::from(r1cs_shape.clone()); + assert_eq!(ccs_shape.num_constraints, NUM_CONSTRAINTS); + assert_eq!(ccs_shape.num_constraints, r1cs_shape.num_constraints); + + assert_eq!(ccs_shape.num_vars, NUM_WITNESS); + assert_eq!(ccs_shape.num_vars, r1cs_shape.num_vars); + + assert_eq!(ccs_shape.num_io, NUM_PUBLIC); + assert_eq!(ccs_shape.num_io, r1cs_shape.num_io); + + assert_eq!(ccs_shape.num_matrices, 3); + assert_eq!(ccs_shape.num_multisets, 3); + assert_eq!(ccs_shape.max_cardinality, 2); + + assert_eq!(ccs_shape.Ms.len(), 3); + assert_eq!(ccs_shape.Ms[0], SparseMatrix::new(&a, NUM_CONSTRAINTS, NUM_WITNESS + NUM_PUBLIC)); + assert_eq!(ccs_shape.Ms[1], SparseMatrix::new(&b, NUM_CONSTRAINTS, NUM_WITNESS + NUM_PUBLIC)); + assert_eq!(ccs_shape.Ms[2], SparseMatrix::new(&c, NUM_CONSTRAINTS, NUM_WITNESS + NUM_PUBLIC)); + + assert_eq!(ccs_shape.cSs.len(), 2); + assert_eq!(ccs_shape.cSs[0], (Fr::ONE, vec![0, 1])); + assert_eq!(ccs_shape.cSs[0], (Fr::ONE.neg(), vec![2])); + + Ok(()) + } + #[test] fn zero_instance_is_satisfied() -> Result<(), Error> { #[rustfmt::skip] @@ -298,4 +411,127 @@ mod tests { ); Ok(()) } + + use ark_spartan::polycommitments::zeromorph::Zeromorph; + use ark_spartan::polycommitments::PCSKeys; + use ark_std::{test_rng, UniformRand}; + + type Z = Zeromorph; + + #[test] + fn zero_instance_is_satisfied_linearized() -> Result<(), Error> { + #[rustfmt::skip] + let a = { + let a: &[&[u64]] = &[ + &[1, 2, 3], + &[3, 4, 5], + &[6, 7, 8], + ]; + to_field_sparse::(a) + }; + + const NUM_CONSTRAINTS: usize = 3; + const NUM_WITNESS: usize = 1; + const NUM_PUBLIC: usize = 2; + + let r1cs_shape: R1CSShape = + R1CSShape::::new(NUM_CONSTRAINTS, NUM_WITNESS, NUM_PUBLIC, &a, &a, &a).unwrap(); + + let ccs_shape = CCSShape::from(r1cs_shape); + + let mut rng = test_rng(); + let SRS = Z::setup(4, b"test", &mut rng).unwrap(); + let PCSKeys { ck, .. } = Z::trim(&SRS, 4); + + let X = to_field_elements::(&[0, 0]); + let W = to_field_elements::(&[0]); + let witness = CCSWitness::::new(&ccs_shape, &W)?; + + let commitment_W = witness.commit_linearized::(&ck); + + let s = (NUM_CONSTRAINTS - 1).checked_ilog2().unwrap_or(0) + 1; + let rs: Vec = (0..s).map(|_| Fr::rand(&mut rng)).collect(); + + let z = [X.as_slice(), W.as_slice()].concat(); + let vs: Vec = ark_std::cfg_iter!(&ccs_shape.Ms) + .map(|M| vec_to_mle(M.multiply_vec(&z).as_slice()).evaluate::(rs.as_slice())) + .collect(); + + let instance = LCCSInstance::::new(&ccs_shape, &commitment_W, &X, &rs, &vs)?; + + ccs_shape.is_satisfied_linearized::(&instance, &witness, &ck)?; + + Ok(()) + } + + #[test] + fn is_satisfied_linearized() -> Result<(), Error> { + let (a, b, c) = { + ( + to_field_sparse::(A), + to_field_sparse::(B), + to_field_sparse::(C), + ) + }; + + const NUM_CONSTRAINTS: usize = 4; + const NUM_WITNESS: usize = 4; + const NUM_PUBLIC: usize = 2; + + let r1cs_shape: R1CSShape = + R1CSShape::::new(NUM_CONSTRAINTS, NUM_WITNESS, NUM_PUBLIC, &a, &b, &c).unwrap(); + + let ccs_shape = CCSShape::from(r1cs_shape); + + let mut rng = test_rng(); + let SRS = Z::setup(8, b"test", &mut rng).unwrap(); + let PCSKeys { ck, .. } = Z::trim(&SRS, 8); + + let X = to_field_elements::(&[1, 35]); + let W = to_field_elements::(&[3, 9, 27, 30]); + let witness = CCSWitness::::new(&ccs_shape, &W)?; + + let commitment_W = witness.commit_linearized::(&ck); + + let s = (NUM_CONSTRAINTS - 1).checked_ilog2().unwrap_or(0) + 1; + let rs: Vec = (0..s).map(|_| Fr::rand(&mut rng)).collect(); + + let z = [X.as_slice(), W.as_slice()].concat(); + let vs: Vec = ark_std::cfg_iter!(&ccs_shape.Ms) + .map(|M| vec_to_mle(M.multiply_vec(&z).as_slice()).evaluate::(rs.as_slice())) + .collect(); + + let instance = LCCSInstance::::new(&ccs_shape, &commitment_W, &X, &rs, &vs)?; + + ccs_shape.is_satisfied_linearized::(&instance, &witness, &ck)?; + + // Change commitment. + let invalid_commitment = commitment_W + commitment_W; + let instance = LCCSInstance::::new(&ccs_shape, &invalid_commitment, &X, &rs, &vs)?; + assert_eq!( + ccs_shape.is_satisfied_linearized(&instance, &witness, &ck), + Err(Error::NotSatisfied) + ); + + // Provide invalid witness. + let invalid_W = to_field_elements::(&[4, 9, 27, 30]); + let invalid_witness = CCSWitness::::new(&ccs_shape, &invalid_W)?; + let commitment_invalid_W = invalid_witness.commit_linearized::(&ck); + + let instance = LCCSInstance::::new(&ccs_shape, &commitment_invalid_W, &X, &rs, &vs)?; + assert_eq!( + ccs_shape.is_satisfied_linearized(&instance, &invalid_witness, &ck), + Err(Error::NotSatisfied) + ); + + // Provide invalid public input. + let invalid_X = to_field_elements::(&[1, 36]); + let instance = LCCSInstance::::new(&ccs_shape, &commitment_W, &invalid_X, &rs, &vs)?; + assert_eq!( + ccs_shape.is_satisfied_linearized(&instance, &witness, &ck), + Err(Error::NotSatisfied) + ); + + Ok(()) + } } From 7b1bb86625e70be7599fbe11bdd4cd1c668e03e4 Mon Sep 17 00:00:00 2001 From: Samuel Judson Date: Thu, 15 Feb 2024 22:55:57 -0500 Subject: [PATCH 22/26] Fix tests. --- nova/src/ccs/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/src/ccs/mod.rs b/nova/src/ccs/mod.rs index 012f30ce0..7e45683e3 100644 --- a/nova/src/ccs/mod.rs +++ b/nova/src/ccs/mod.rs @@ -304,7 +304,7 @@ mod tests { assert_eq!(ccs_shape.num_io, r1cs_shape.num_io); assert_eq!(ccs_shape.num_matrices, 3); - assert_eq!(ccs_shape.num_multisets, 3); + assert_eq!(ccs_shape.num_multisets, 2); assert_eq!(ccs_shape.max_cardinality, 2); assert_eq!(ccs_shape.Ms.len(), 3); @@ -314,7 +314,7 @@ mod tests { assert_eq!(ccs_shape.cSs.len(), 2); assert_eq!(ccs_shape.cSs[0], (Fr::ONE, vec![0, 1])); - assert_eq!(ccs_shape.cSs[0], (Fr::ONE.neg(), vec![2])); + assert_eq!(ccs_shape.cSs[1], (Fr::ONE.neg(), vec![2])); Ok(()) } From 63e7271c122c0f034368b8090ac68b4a03c161f3 Mon Sep 17 00:00:00 2001 From: Samuel Judson Date: Thu, 15 Feb 2024 22:58:18 -0500 Subject: [PATCH 23/26] Fix fmt. --- nova/src/ccs/mod.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/nova/src/ccs/mod.rs b/nova/src/ccs/mod.rs index 7e45683e3..fca5c223a 100644 --- a/nova/src/ccs/mod.rs +++ b/nova/src/ccs/mod.rs @@ -296,7 +296,7 @@ mod tests { let ccs_shape = CCSShape::from(r1cs_shape.clone()); assert_eq!(ccs_shape.num_constraints, NUM_CONSTRAINTS); assert_eq!(ccs_shape.num_constraints, r1cs_shape.num_constraints); - + assert_eq!(ccs_shape.num_vars, NUM_WITNESS); assert_eq!(ccs_shape.num_vars, r1cs_shape.num_vars); @@ -308,9 +308,18 @@ mod tests { assert_eq!(ccs_shape.max_cardinality, 2); assert_eq!(ccs_shape.Ms.len(), 3); - assert_eq!(ccs_shape.Ms[0], SparseMatrix::new(&a, NUM_CONSTRAINTS, NUM_WITNESS + NUM_PUBLIC)); - assert_eq!(ccs_shape.Ms[1], SparseMatrix::new(&b, NUM_CONSTRAINTS, NUM_WITNESS + NUM_PUBLIC)); - assert_eq!(ccs_shape.Ms[2], SparseMatrix::new(&c, NUM_CONSTRAINTS, NUM_WITNESS + NUM_PUBLIC)); + assert_eq!( + ccs_shape.Ms[0], + SparseMatrix::new(&a, NUM_CONSTRAINTS, NUM_WITNESS + NUM_PUBLIC) + ); + assert_eq!( + ccs_shape.Ms[1], + SparseMatrix::new(&b, NUM_CONSTRAINTS, NUM_WITNESS + NUM_PUBLIC) + ); + assert_eq!( + ccs_shape.Ms[2], + SparseMatrix::new(&c, NUM_CONSTRAINTS, NUM_WITNESS + NUM_PUBLIC) + ); assert_eq!(ccs_shape.cSs.len(), 2); assert_eq!(ccs_shape.cSs[0], (Fr::ONE, vec![0, 1])); From eef63c833ee86097e59f9da98151d6ec1b0c8c04 Mon Sep 17 00:00:00 2001 From: Samuel Judson Date: Fri, 16 Feb 2024 12:50:13 -0500 Subject: [PATCH 24/26] Remove files accidently restored during rebase. --- nova/src/ccs/lccs.rs | 364 ------------------------------------- nova/src/ccs/vector_ops.rs | 33 ---- nova/src/r1cs/sparse.rs | 205 --------------------- 3 files changed, 602 deletions(-) delete mode 100644 nova/src/ccs/lccs.rs delete mode 100644 nova/src/ccs/vector_ops.rs delete mode 100644 nova/src/r1cs/sparse.rs diff --git a/nova/src/ccs/lccs.rs b/nova/src/ccs/lccs.rs deleted file mode 100644 index 60c3656a3..000000000 --- a/nova/src/ccs/lccs.rs +++ /dev/null @@ -1,364 +0,0 @@ -use ark_ec::{AdditiveGroup, CurveGroup}; -use ark_ff::Field; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_spartan::{ - dense_mlpoly::DensePolynomial as DenseMultilinearExtension, - polycommitments::PolyCommitmentScheme, - sparse_mlpoly::SparsePolynomial as SparseMultilinearExtension, -}; - -use std::ops::Neg; - -#[cfg(feature = "parallel")] -use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator}; - -use super::super::r1cs::R1CSShape; -pub use super::super::sparse::{MatrixRef, SparseMatrix}; -use super::mle::{compose_mle_input, matrix_to_mle, vec_to_mle}; - -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum Error { - InvalidWitnessLength, - InvalidInputLength, - InvalidEvaluationPoint, - InvalidTargets, - NotSatisfied, -} - -pub struct LCCSShape { - /// `m` in the CCS/HyperNova papers. - pub num_constraints: usize, - /// Witness length. - /// - /// `m - l - 1` in the CCS/HyperNova papers. - pub num_vars: usize, - /// Length of the public input `X`. It is expected to have a leading - /// `ScalarField` element (`u`), thus this field must be non-zero. - /// - /// `l + 1`, w.r.t. the CCS/HyperNova papers. - pub num_io: usize, - /// Number of matrices. - /// - /// `t` in the CCS/HyperNova papers. - pub num_matrices: usize, - /// Number of multisets. - /// - /// `q` in the CCS/HyperNova papers. - pub num_multisets: usize, - /// Max cardinality of the multisets. - /// - /// `d` in the CCS/HyperNova papers. - pub max_cardinality: usize, - /// Set of constraint matrices. - pub Ms: Vec>, - /// Multisets of selector indices, each paired with a constant multiplier. - pub cSs: Vec<(G::ScalarField, Vec)>, -} - -impl LCCSShape { - pub fn is_satisfied>( - &self, - U: &LCCSInstance, - W: &LCCSWitness, - ck: &C::PolyCommitmentKey, - ) -> Result<(), Error> { - assert_eq!(U.X.len(), self.num_io); - let X = vec_to_mle(U.X.as_slice()); - let z = DenseMultilinearExtension::::merge(&[X, W.W.to_owned()]); - - let n = (self.num_io + self.num_vars).next_power_of_two(); - let s = (n - 1).checked_ilog2().unwrap_or(0) + 1; - - let Mzs: Vec = ark_std::cfg_iter!(&self.Ms) - .map(|M| { - (0..n) - .map(|y| { - let ry = compose_mle_input(U.rs.as_slice(), y, s as usize); - M.evaluate(ry.as_slice()) * z.evaluate::(&ry[U.rs.len()..]) - }) - .sum() - }) - .collect(); - - if ark_std::cfg_into_iter!(0..self.num_matrices).any(|idx| Mzs[idx] != U.vs[idx]) { - return Err(Error::NotSatisfied); - } - - if U.commitment_W != C::commit(&W.W, ck) { - return Err(Error::NotSatisfied); - } - - Ok(()) - } -} - -/// Create an object of type `LCCSShape` from the specified R1CS shape -impl From> for LCCSShape { - fn from(shape: R1CSShape) -> Self { - let rows = shape.num_constraints; - let columns = shape.num_io + shape.num_vars; - - Self { - num_constraints: shape.num_constraints, - num_io: shape.num_io, - num_vars: shape.num_vars, - num_matrices: 3, - num_multisets: 2, - max_cardinality: 2, - Ms: vec![ - matrix_to_mle(rows, columns, &shape.A), - matrix_to_mle(rows, columns, &shape.B), - matrix_to_mle(rows, columns, &shape.C), - ], - cSs: vec![ - (G::ScalarField::ONE, vec![0, 1]), - (G::ScalarField::ONE.neg(), vec![2]), - ], - } - } -} - -/// A type that holds a witness for a given LCCS instance. -#[derive(Clone, Debug, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)] -pub struct LCCSWitness { - pub W: DenseMultilinearExtension, -} - -/// A type that holds an LCCS instance. -#[derive(CanonicalSerialize, CanonicalDeserialize)] -pub struct LCCSInstance> { - /// Commitment to MLE of witness. - /// - /// C in HyperNova/CCS papers. - pub commitment_W: C::Commitment, - /// X is assumed to start with a `ScalarField` field element `u`. - pub X: Vec, - /// (Random) evaluation point - pub rs: Vec, - /// Evaluation targets - pub vs: Vec, -} - -impl> Clone for LCCSInstance { - fn clone(&self) -> Self { - Self { - commitment_W: self.commitment_W.clone(), - X: self.X.clone(), - rs: self.rs.clone(), - vs: self.vs.clone(), - } - } -} - -impl> PartialEq for LCCSInstance { - fn eq(&self, other: &Self) -> bool { - self.commitment_W == other.commitment_W && self.X == other.X - } -} - -impl> Eq for LCCSInstance {} - -impl LCCSWitness { - /// A method to create a witness object using a vector of scalars. - pub fn new(shape: &LCCSShape, W: &[G::ScalarField]) -> Result { - if shape.num_vars != W.len() { - Err(Error::InvalidWitnessLength) - } else { - Ok(Self { W: vec_to_mle(W) }) - } - } - - pub fn zero(shape: &LCCSShape) -> Self { - Self { - W: vec_to_mle(vec![G::ScalarField::ZERO; shape.num_vars].as_slice()), - } - } - - /// Commits to the witness using the supplied key - pub fn commit>(&self, ck: &C::PolyCommitmentKey) -> C::Commitment { - C::commit(&self.W, ck) - } -} - -impl> LCCSInstance { - /// A method to create an instance object using constituent elements. - pub fn new( - shape: &LCCSShape, - commitment_W: &C::Commitment, - X: &[G::ScalarField], - rs: &[G::ScalarField], - vs: &[G::ScalarField], - ) -> Result { - if X.is_empty() || shape.num_io != X.len() { - Err(Error::InvalidInputLength) - } else if ((shape.num_constraints - 1).checked_ilog2().unwrap_or(0) + 1) != rs.len() as u32 - { - Err(Error::InvalidEvaluationPoint) - } else if shape.num_matrices != vs.len() { - Err(Error::InvalidTargets) - } else { - Ok(Self { - commitment_W: commitment_W.clone(), - X: X.to_owned(), - rs: rs.to_owned(), - vs: vs.to_owned(), - }) - } - } -} - -#[cfg(test)] -mod tests { - #![allow(non_upper_case_globals)] - #![allow(clippy::needless_range_loop)] - - use super::*; - - use ark_spartan::polycommitments::zeromorph::Zeromorph; - use ark_spartan::polycommitments::PCSKeys; - use ark_std::{test_rng, UniformRand}; - use ark_test_curves::bls12_381::{Bls12_381 as E, Fr, G1Projective as G}; - - use crate::r1cs::tests::{to_field_elements, to_field_sparse, A, B, C}; - - type Z = Zeromorph; - - #[test] - fn zero_instance_is_satisfied() -> Result<(), Error> { - #[rustfmt::skip] - let a = { - let a: &[&[u64]] = &[ - &[1, 2, 3], - &[3, 4, 5], - &[6, 7, 8], - ]; - to_field_sparse::(a) - }; - - const NUM_CONSTRAINTS: usize = 3; - const NUM_WITNESS: usize = 1; - const NUM_PUBLIC: usize = 2; - - let r1cs_shape: R1CSShape = - R1CSShape::::new(NUM_CONSTRAINTS, NUM_WITNESS, NUM_PUBLIC, &a, &a, &a).unwrap(); - - let lccs_shape = LCCSShape::from(r1cs_shape); - - let mut rng = test_rng(); - let SRS = Z::setup(4, b"test", &mut rng).unwrap(); - let PCSKeys { ck, .. } = Z::trim(&SRS, 4); - - let X = to_field_elements::(&[0, 0]); - let W = to_field_elements::(&[0]); - let witness = LCCSWitness::::new(&lccs_shape, &W)?; - - let commitment_W = witness.commit::(&ck); - - let s1 = (NUM_CONSTRAINTS - 1).checked_ilog2().unwrap_or(0) + 1; - let rs: Vec = (0..s1).map(|_| Fr::rand(&mut rng)).collect(); - - let z = DenseMultilinearExtension::::merge(&[vec_to_mle(&X), vec_to_mle(&W)]); - - let n = (NUM_WITNESS + NUM_PUBLIC).next_power_of_two(); - let s2 = (n - 1).checked_ilog2().unwrap_or(0) + 1; - - let vs: Vec = ark_std::cfg_iter!(&lccs_shape.Ms) - .map(|M| { - (0..n) - .map(|idx| { - let ry = compose_mle_input(rs.as_slice(), idx, s2 as usize); - M.evaluate(ry.as_slice()) * z.evaluate::(&ry[rs.len()..]) - }) - .sum() - }) - .collect(); - - let instance = LCCSInstance::::new(&lccs_shape, &commitment_W, &X, &rs, &vs)?; - - lccs_shape.is_satisfied::(&instance, &witness, &ck)?; - - Ok(()) - } - - #[test] - fn is_satisfied() -> Result<(), Error> { - let (a, b, c) = { - ( - to_field_sparse::(A), - to_field_sparse::(B), - to_field_sparse::(C), - ) - }; - - const NUM_CONSTRAINTS: usize = 4; - const NUM_WITNESS: usize = 4; - const NUM_PUBLIC: usize = 2; - - let r1cs_shape: R1CSShape = - R1CSShape::::new(NUM_CONSTRAINTS, NUM_WITNESS, NUM_PUBLIC, &a, &b, &c).unwrap(); - - let lccs_shape = LCCSShape::from(r1cs_shape); - - let mut rng = test_rng(); - let SRS = Z::setup(8, b"test", &mut rng).unwrap(); - let PCSKeys { ck, .. } = Z::trim(&SRS, 8); - - let X = to_field_elements::(&[1, 35]); - let W = to_field_elements::(&[3, 9, 27, 30]); - let witness = LCCSWitness::::new(&lccs_shape, &W)?; - - let commitment_W = witness.commit::(&ck); - - let s1 = (NUM_CONSTRAINTS - 1).checked_ilog2().unwrap_or(0) + 1; - let rs: Vec = (0..s1).map(|_| Fr::rand(&mut rng)).collect(); - - let z = DenseMultilinearExtension::::merge(&[vec_to_mle(&X), vec_to_mle(&W)]); - - let n = (NUM_WITNESS + NUM_PUBLIC).next_power_of_two(); - let s2 = (n - 1).checked_ilog2().unwrap_or(0) + 1; - - let vs: Vec = ark_std::cfg_iter!(&lccs_shape.Ms) - .map(|M| { - (0..n) - .map(|y| { - let ry = compose_mle_input(rs.as_slice(), y, s2 as usize); - M.evaluate(ry.as_slice()) * z.evaluate::(&ry[rs.len()..]) - }) - .sum() - }) - .collect(); - - let instance = LCCSInstance::::new(&lccs_shape, &commitment_W, &X, &rs, &vs)?; - - lccs_shape.is_satisfied::(&instance, &witness, &ck)?; - - // Change commitment. - let invalid_commitment = commitment_W + commitment_W; - let instance = LCCSInstance::::new(&lccs_shape, &invalid_commitment, &X, &rs, &vs)?; - assert_eq!( - lccs_shape.is_satisfied(&instance, &witness, &ck), - Err(Error::NotSatisfied) - ); - - // Provide invalid witness. - let invalid_W = to_field_elements::(&[4, 9, 27, 30]); - let invalid_witness = LCCSWitness::::new(&lccs_shape, &invalid_W)?; - let commitment_invalid_W = invalid_witness.commit::(&ck); - - let instance = LCCSInstance::::new(&lccs_shape, &commitment_invalid_W, &X, &rs, &vs)?; - assert_eq!( - lccs_shape.is_satisfied(&instance, &invalid_witness, &ck), - Err(Error::NotSatisfied) - ); - - // Provide invalid public input. - let invalid_X = to_field_elements::(&[1, 36]); - let instance = LCCSInstance::::new(&lccs_shape, &commitment_W, &invalid_X, &rs, &vs)?; - assert_eq!( - lccs_shape.is_satisfied(&instance, &witness, &ck), - Err(Error::NotSatisfied) - ); - - Ok(()) - } -} diff --git a/nova/src/ccs/vector_ops.rs b/nova/src/ccs/vector_ops.rs deleted file mode 100644 index 19a20cae4..000000000 --- a/nova/src/ccs/vector_ops.rs +++ /dev/null @@ -1,33 +0,0 @@ -use ark_ff::PrimeField; -use ark_std::iter::zip; - -/// Multiply two vectors element wise (hadamard product). -pub fn elem_mul(us: &Vec, vs: &Vec) -> Vec { - assert_eq!(us.len(), vs.len()); - - elem_mul_unchecked(us, vs) -} - -/// Multiply two vectors element wise (hadamard product). -/// This does not check that the vector shapes are compatible. -pub fn elem_mul_unchecked(us: &Vec, vs: &Vec) -> Vec { - zip(us, vs).map(|(u, v)| *u * v).collect() -} - -/// Multiply a vector by a scalar. -pub fn scalar_mul(us: &[F], c: &F) -> Vec { - us.iter().map(|u| *u * c).collect() -} - -/// Add two vectors together element wise. -pub fn elem_add(us: &Vec, vs: &Vec) -> Vec { - assert_eq!(us.len(), vs.len()); - - elem_add_unchecked(us, vs) -} - -/// Add two vectors together element wise -/// This does not check that the vector shapes are compatible. -pub fn elem_add_unchecked(us: &Vec, vs: &Vec) -> Vec { - zip(us, vs).map(|(u, v)| *u + v).collect() -} diff --git a/nova/src/r1cs/sparse.rs b/nova/src/r1cs/sparse.rs deleted file mode 100644 index ef0660857..000000000 --- a/nova/src/r1cs/sparse.rs +++ /dev/null @@ -1,205 +0,0 @@ -//! # Sparse Matrices -//! -//! This module defines a custom implementation of CSR/CSC sparse matrices. -//! Specifically, we implement sparse matrix / dense vector multiplication -//! to compute the `A z`, `B z`, and `C z` in Nova. - -use ark_ff::PrimeField; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; - -#[cfg(feature = "parallel")] -use rayon::{iter::ParallelIterator, slice::ParallelSlice}; - -pub type MatrixRef<'a, F> = &'a [Vec<(F, usize)>]; - -/// CSR format sparse matrix, We follow the names used by scipy. -/// Detailed explanation here: https://stackoverflow.com/questions/52299420/scipy-csr-matrix-understand-indptr -#[derive(Clone, Debug, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)] -pub struct SparseMatrix { - /// all non-zero values in the matrix - pub data: Vec, - /// column indices - pub indices: Vec, - /// row information - pub indptr: Vec, - /// number of columns - pub cols: usize, -} - -impl SparseMatrix { - /// 0x0 empty matrix - pub fn empty() -> Self { - SparseMatrix { - data: vec![], - indices: vec![], - indptr: vec![0], - cols: 0, - } - } - - /// Construct from the COO representation; - /// We assume that the rows are sorted during construction. - pub fn new(matrix: MatrixRef<'_, F>, rows: usize, cols: usize) -> Self { - let mut new_matrix = vec![vec![]; rows]; - let matrix_iter = matrix - .iter() - .enumerate() - .flat_map(|(i, row)| row.iter().map(move |&(f, j)| (i, j, f))); - for (row, col, val) in matrix_iter { - new_matrix[row].push((col, val)); - } - - for row in new_matrix.iter() { - assert!(row.windows(2).all(|w| w[0].0 < w[1].0)); - } - - let mut indptr = vec![0; rows + 1]; - for (i, row) in new_matrix.iter().enumerate() { - indptr[i + 1] = indptr[i] + row.len(); - } - - let mut indices = vec![]; - let mut data = vec![]; - for row in new_matrix { - let (idx, val): (Vec, Vec) = row.into_iter().unzip(); - indices.extend(idx); - data.extend(val); - } - - SparseMatrix { data, indices, indptr, cols } - } - - /// Retrieves the data for row slice [i..j] from `ptrs`. - /// We assume that `ptrs` is indexed from `indptrs` and do not check if the - /// returned slice is actually a valid row. - pub fn get_row_unchecked(&self, ptrs: &[usize; 2]) -> impl Iterator { - self.data[ptrs[0]..ptrs[1]] - .iter() - .zip(&self.indices[ptrs[0]..ptrs[1]]) - } - - /// Multiply by a dense vector; - pub fn multiply_vec(&self, vector: &[F]) -> Vec { - assert_eq!(self.cols, vector.len()); - - self.multiply_vec_unchecked(vector) - } - - /// Multiply by a dense vector; - /// This does not check that the shape of the matrix/vector are compatible. - pub fn multiply_vec_unchecked(&self, vector: &[F]) -> Vec { - #[cfg(feature = "parallel")] - let iter = self.indptr.par_windows(2); - #[cfg(not(feature = "parallel"))] - let iter = self.indptr.windows(2); - - iter.map(|ptrs| { - self.get_row_unchecked(ptrs.try_into().unwrap()) - .map(|(val, col_idx)| *val * vector[*col_idx]) - .sum() - }) - .collect() - } - - /// number of non-zero entries - pub fn len(&self) -> usize { - *self.indptr.last().unwrap() - } - - /// empty matrix - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// returns a custom iterator - pub fn iter(&self) -> Iter<'_, F> { - let mut row = 0; - while self.indptr[row + 1] == 0 { - row += 1; - } - Iter { - matrix: self, - row, - i: 0, - nnz: *self.indptr.last().unwrap(), - } - } -} - -/// Iterator for sparse matrix -pub struct Iter<'a, F: PrimeField> { - matrix: &'a SparseMatrix, - row: usize, - i: usize, - nnz: usize, -} - -impl<'a, F: PrimeField> Iterator for Iter<'a, F> { - type Item = (usize, usize, F); - - fn next(&mut self) -> Option { - // are we at the end? - if self.i == self.nnz { - return None; - } - - // compute current item - let curr_item = ( - self.row, - self.matrix.indices[self.i], - self.matrix.data[self.i], - ); - - // advance the iterator - self.i += 1; - // edge case at the end - if self.i == self.nnz { - return Some(curr_item); - } - // if `i` has moved to next row - while self.i >= self.matrix.indptr[self.row + 1] { - self.row += 1; - } - - Some(curr_item) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - type Fr = ark_test_curves::bls12_381::Fr; - - #[test] - fn test_matrix_creation() { - let matrix_data = vec![ - vec![(Fr::from(2), 1)], - vec![(Fr::from(3), 2)], - vec![(Fr::from(4), 0)], - ]; - let sparse_matrix = SparseMatrix::::new(&matrix_data, 3, 3); - - assert_eq!( - sparse_matrix.data, - vec![Fr::from(2), Fr::from(3), Fr::from(4)] - ); - assert_eq!(sparse_matrix.indices, vec![1, 2, 0]); - assert_eq!(sparse_matrix.indptr, vec![0, 1, 2, 3]); - } - - #[test] - fn test_matrix_vector_multiplication() { - let matrix_data = vec![ - vec![(Fr::from(2), 1), (Fr::from(7), 2)], - vec![(Fr::from(3), 2)], - vec![(Fr::from(4), 0)], - ]; - let sparse_matrix = SparseMatrix::::new(&matrix_data, 3, 3); - let vector = vec![Fr::from(1), Fr::from(2), Fr::from(3)]; - - let result = sparse_matrix.multiply_vec(&vector); - - assert_eq!(result, vec![Fr::from(25), Fr::from(9), Fr::from(4)]); - } -} From cc96251a3e9093ab4c184cc3bf0c6390209ee1f1 Mon Sep 17 00:00:00 2001 From: Samuel Judson Date: Tue, 20 Feb 2024 12:38:28 -0500 Subject: [PATCH 25/26] Move to using polynomial commitment exlcusively. --- nova/src/ccs/mod.rs | 100 ++++++++++++++++++++------------------------ 1 file changed, 46 insertions(+), 54 deletions(-) diff --git a/nova/src/ccs/mod.rs b/nova/src/ccs/mod.rs index fca5c223a..20bad813e 100644 --- a/nova/src/ccs/mod.rs +++ b/nova/src/ccs/mod.rs @@ -12,8 +12,6 @@ use rayon::iter::{ IntoParallelRefMutIterator, ParallelIterator, }; -use super::commitment::CommitmentScheme; - use super::r1cs::R1CSShape; pub use super::sparse::{MatrixRef, SparseMatrix}; use mle::vec_to_mle; @@ -63,11 +61,11 @@ pub struct CCSShape { impl CCSShape { /// Checks if the CCS instance together with the witness `W` satisfies the CCS constraints determined by `shape`. - pub fn is_satisfied>( + pub fn is_satisfied>( &self, U: &CCSInstance, W: &CCSWitness, - pp: &C::PP, + ck: &C::PolyCommitmentKey, ) -> Result<(), Error> { assert_eq!(W.W.len(), self.num_vars); assert_eq!(U.X.len(), self.num_io); @@ -96,7 +94,7 @@ impl CCSShape { return Err(Error::NotSatisfied); } - if U.commitment_W != W.commit::(pp) { + if U.commitment_W != W.commit::(ck) { return Err(Error::NotSatisfied); } @@ -121,7 +119,7 @@ impl CCSShape { return Err(Error::NotSatisfied); } - if U.commitment_W != W.commit_linearized::(ck) { + if U.commitment_W != W.commit::(ck) { return Err(Error::NotSatisfied); } @@ -155,23 +153,14 @@ pub struct CCSWitness { } /// A type that holds an CCS instance. -#[derive(Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct CCSInstance> { +#[derive(Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] +pub struct CCSInstance> { /// Commitment to witness. pub commitment_W: C::Commitment, /// X is assumed to start with a `ScalarField::ONE`. pub X: Vec, } -impl> Clone for CCSInstance { - fn clone(&self) -> Self { - Self { - commitment_W: self.commitment_W, - X: self.X.clone(), - } - } -} - impl CCSWitness { /// A method to create a witness object using a vector of scalars. pub fn new(shape: &CCSShape, W: &[G::ScalarField]) -> Result { @@ -188,13 +177,8 @@ impl CCSWitness { } } - /// Commits to the witness using the supplied generators - pub fn commit>(&self, pp: &C::PP) -> C::Commitment { - C::commit(pp, &self.W) - } - /// Commits to the witness as a polynomial using the supplied key - fn commit_linearized>( + fn commit>( &self, ck: &C::PolyCommitmentKey, ) -> C::Commitment { @@ -202,7 +186,7 @@ impl CCSWitness { } } -impl> CCSInstance { +impl> CCSInstance { /// A method to create an instance object using constituent elements. pub fn new( shape: &CCSShape, @@ -216,7 +200,7 @@ impl> CCSInstance { Err(Error::InvalidInputLength) } else { Ok(Self { - commitment_W: *commitment_W, + commitment_W: commitment_W.clone(), X: X.to_owned(), }) } @@ -271,9 +255,14 @@ mod tests { #![allow(clippy::needless_range_loop)] use super::*; - use crate::pedersen::PedersenCommitment; + + use ark_spartan::polycommitments::zeromorph::Zeromorph; + use ark_spartan::polycommitments::PCSKeys; + use ark_std::{test_rng, UniformRand}; use ark_test_curves::bls12_381::{Bls12_381 as E, Fr, G1Projective as G}; + type Z = Zeromorph; + use crate::r1cs::tests::{to_field_elements, to_field_sparse, A, B, C}; #[test] @@ -344,20 +333,24 @@ mod tests { const NUM_WITNESS: usize = 1; const NUM_PUBLIC: usize = 2; - let pp = PedersenCommitment::::setup(NUM_WITNESS, &()); let r1cs_shape: R1CSShape = R1CSShape::::new(NUM_CONSTRAINTS, NUM_WITNESS, NUM_PUBLIC, &a, &a, &a).unwrap(); let ccs_shape = CCSShape::from(r1cs_shape); + let mut rng = test_rng(); + let SRS = Z::setup(8, b"test", &mut rng).unwrap(); + let PCSKeys { ck, .. } = Z::trim(&SRS, 8); + let X = to_field_elements::(&[0, 0]); let W = to_field_elements::(&[0]); - let commitment_W = PedersenCommitment::::commit(&pp, &W); - - let instance = CCSInstance::>::new(&ccs_shape, &commitment_W, &X)?; let witness = CCSWitness::::new(&ccs_shape, &W)?; - ccs_shape.is_satisfied(&instance, &witness, &pp)?; + let commitment_W = witness.commit::(&ck); + + let instance = CCSInstance::::new(&ccs_shape, &commitment_W, &X)?; + + ccs_shape.is_satisfied(&instance, &witness, &ck)?; Ok(()) } @@ -375,58 +368,57 @@ mod tests { const NUM_WITNESS: usize = 4; const NUM_PUBLIC: usize = 2; - let pp = PedersenCommitment::::setup(NUM_WITNESS, &()); let r1cs_shape: R1CSShape = R1CSShape::::new(NUM_CONSTRAINTS, NUM_WITNESS, NUM_PUBLIC, &a, &b, &c).unwrap(); let ccs_shape = CCSShape::from(r1cs_shape); + let mut rng = test_rng(); + let SRS = Z::setup(8, b"test", &mut rng).unwrap(); + let PCSKeys { ck, .. } = Z::trim(&SRS, 8); + let X = to_field_elements::(&[1, 35]); let W = to_field_elements::(&[3, 9, 27, 30]); - let commitment_W = PedersenCommitment::::commit(&pp, &W); - - let instance = CCSInstance::>::new(&ccs_shape, &commitment_W, &X)?; let witness = CCSWitness::::new(&ccs_shape, &W)?; - ccs_shape.is_satisfied(&instance, &witness, &pp)?; + let commitment_W = witness.commit::(&ck); + + let instance = CCSInstance::::new(&ccs_shape, &commitment_W, &X)?; + + ccs_shape.is_satisfied(&instance, &witness, &ck)?; // Change commitment. - let invalid_commitment = commitment_W.double(); + let invalid_commitment = commitment_W + commitment_W; let instance = - CCSInstance::>::new(&ccs_shape, &invalid_commitment, &X)?; + CCSInstance::::new(&ccs_shape, &invalid_commitment, &X)?; assert_eq!( - ccs_shape.is_satisfied(&instance, &witness, &pp), + ccs_shape.is_satisfied(&instance, &witness, &ck), Err(Error::NotSatisfied) ); // Provide invalid witness. let invalid_W = to_field_elements::(&[4, 9, 27, 30]); - let commitment_invalid_W = PedersenCommitment::::commit(&pp, &W); - let instance = - CCSInstance::>::new(&ccs_shape, &commitment_invalid_W, &X)?; let invalid_witness = CCSWitness::::new(&ccs_shape, &invalid_W)?; + let commitment_invalid_W = invalid_witness.commit::(&ck); + + let instance = + CCSInstance::::new(&ccs_shape, &commitment_invalid_W, &X)?; assert_eq!( - ccs_shape.is_satisfied(&instance, &invalid_witness, &pp), + ccs_shape.is_satisfied(&instance, &invalid_witness, &ck), Err(Error::NotSatisfied) ); // Provide invalid public input. let invalid_X = to_field_elements::(&[1, 36]); let instance = - CCSInstance::>::new(&ccs_shape, &commitment_W, &invalid_X)?; + CCSInstance::::new(&ccs_shape, &commitment_W, &invalid_X)?; assert_eq!( - ccs_shape.is_satisfied(&instance, &witness, &pp), + ccs_shape.is_satisfied(&instance, &witness, &ck), Err(Error::NotSatisfied) ); Ok(()) } - use ark_spartan::polycommitments::zeromorph::Zeromorph; - use ark_spartan::polycommitments::PCSKeys; - use ark_std::{test_rng, UniformRand}; - - type Z = Zeromorph; - #[test] fn zero_instance_is_satisfied_linearized() -> Result<(), Error> { #[rustfmt::skip] @@ -456,7 +448,7 @@ mod tests { let W = to_field_elements::(&[0]); let witness = CCSWitness::::new(&ccs_shape, &W)?; - let commitment_W = witness.commit_linearized::(&ck); + let commitment_W = witness.commit::(&ck); let s = (NUM_CONSTRAINTS - 1).checked_ilog2().unwrap_or(0) + 1; let rs: Vec = (0..s).map(|_| Fr::rand(&mut rng)).collect(); @@ -500,7 +492,7 @@ mod tests { let W = to_field_elements::(&[3, 9, 27, 30]); let witness = CCSWitness::::new(&ccs_shape, &W)?; - let commitment_W = witness.commit_linearized::(&ck); + let commitment_W = witness.commit::(&ck); let s = (NUM_CONSTRAINTS - 1).checked_ilog2().unwrap_or(0) + 1; let rs: Vec = (0..s).map(|_| Fr::rand(&mut rng)).collect(); @@ -525,7 +517,7 @@ mod tests { // Provide invalid witness. let invalid_W = to_field_elements::(&[4, 9, 27, 30]); let invalid_witness = CCSWitness::::new(&ccs_shape, &invalid_W)?; - let commitment_invalid_W = invalid_witness.commit_linearized::(&ck); + let commitment_invalid_W = invalid_witness.commit::(&ck); let instance = LCCSInstance::::new(&ccs_shape, &commitment_invalid_W, &X, &rs, &vs)?; assert_eq!( From 839cc60de41bb7c7e4aa873a7176a69fe0494ddd Mon Sep 17 00:00:00 2001 From: Samuel Judson Date: Tue, 20 Feb 2024 12:51:11 -0500 Subject: [PATCH 26/26] Fix formatting. --- nova/src/ccs/mod.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/nova/src/ccs/mod.rs b/nova/src/ccs/mod.rs index 20bad813e..f1a8135ec 100644 --- a/nova/src/ccs/mod.rs +++ b/nova/src/ccs/mod.rs @@ -178,10 +178,7 @@ impl CCSWitness { } /// Commits to the witness as a polynomial using the supplied key - fn commit>( - &self, - ck: &C::PolyCommitmentKey, - ) -> C::Commitment { + fn commit>(&self, ck: &C::PolyCommitmentKey) -> C::Commitment { C::commit(&vec_to_mle(&self.W), ck) } } @@ -389,8 +386,7 @@ mod tests { // Change commitment. let invalid_commitment = commitment_W + commitment_W; - let instance = - CCSInstance::::new(&ccs_shape, &invalid_commitment, &X)?; + let instance = CCSInstance::::new(&ccs_shape, &invalid_commitment, &X)?; assert_eq!( ccs_shape.is_satisfied(&instance, &witness, &ck), Err(Error::NotSatisfied) @@ -401,8 +397,7 @@ mod tests { let invalid_witness = CCSWitness::::new(&ccs_shape, &invalid_W)?; let commitment_invalid_W = invalid_witness.commit::(&ck); - let instance = - CCSInstance::::new(&ccs_shape, &commitment_invalid_W, &X)?; + let instance = CCSInstance::::new(&ccs_shape, &commitment_invalid_W, &X)?; assert_eq!( ccs_shape.is_satisfied(&instance, &invalid_witness, &ck), Err(Error::NotSatisfied) @@ -410,8 +405,7 @@ mod tests { // Provide invalid public input. let invalid_X = to_field_elements::(&[1, 36]); - let instance = - CCSInstance::::new(&ccs_shape, &commitment_W, &invalid_X)?; + let instance = CCSInstance::::new(&ccs_shape, &commitment_W, &invalid_X)?; assert_eq!( ccs_shape.is_satisfied(&instance, &witness, &ck), Err(Error::NotSatisfied)