Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add initial experimental HyperNova multifolding support #175

Draft
wants to merge 102 commits into
base: main
Choose a base branch
from
Draft
Changes from 1 commit
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
789354a
Add utils for hadamard and matrix vector product
oskarth May 24, 2023
5a025c6
utils: Add more utils, sparse matrix vector product etc
oskarth May 25, 2023
a3e2182
feat: First cut of CCS (#14)
oskarth May 26, 2023
5d4b110
refactor: Remove digest
oskarth May 26, 2023
60bbec2
feat(CCS): Add from_r1cs and get rid of hardcoded constants
oskarth May 26, 2023
4f5e784
fix(CCS): Add commitment key and CCS is_sat test
oskarth May 26, 2023
f72c49b
style(ccs): Remove old comments and unused code, clarify todos
oskarth Jun 2, 2023
4470d92
refactor(ccs): Add experimental hypernova feature flag (on by default)
oskarth Jun 2, 2023
9182cc3
chore(ccs): Turn off hypernova feature flag by default
oskarth Jun 2, 2023
db6add0
chore: update dep
oskarth Jun 2, 2023
2a59d80
chore: Cargo.toml
oskarth Jun 5, 2023
85f4e22
add doc and tests for spartan/polynomial.rs
wangtsiao May 27, 2023
5223fea
use pasta instead of defining new fp
wangtsiao May 31, 2023
6a45489
wrap link with <> in comment
wangtsiao Jun 1, 2023
9598d1d
fmt(spartan): cargo fmt
oskarth Jun 5, 2023
64b7bd7
Remove: Clippy unused fn lint
CPerezz Jun 5, 2023
8db47a5
change: Impl SparseMatrix type
CPerezz Jun 5, 2023
ac57cb6
change: Adopt SparseMatrix type and make `pad` recieve &mut
CPerezz Jun 5, 2023
d373753
Fix: Update tests to match new trait bounds
CPerezz Jun 5, 2023
ed36b4a
feat(ccs): Sketch out basic CCCS/LCCCS associated data structures
oskarth Jun 7, 2023
0262dc6
feat(utils): Add SparseMatrix n_rows/n_cols utils
oskarth Jun 7, 2023
f1a8834
fix(ccs): Calculcate m n_rows correctly for a set of sparse matrices
oskarth Jun 7, 2023
d1e63a0
feat(ccs): Add m, n, s and s_prime to CCS struct
oskarth Jun 7, 2023
0192d63
WIP CCCS
oskarth Jun 12, 2023
49455f8
WIP: CCS MLE stuff
oskarth Jun 13, 2023
1a9244e
fix(utils): fix sparse_matrix_to_mlp test
oskarth Jun 14, 2023
93fc061
refactor(ccs): change c to be Scalar
oskarth Jun 14, 2023
dbc0c22
feat(polynomial): Add convenience functions for MLP
oskarth Jun 14, 2023
48c1ac9
wip(ccs): compute_g function
oskarth Jun 14, 2023
0a473b0
refactor: use shorthand syntax for add and mul operations
oskarth Jun 15, 2023
e404742
feat: Add BooleanHypercube to evaluate multilinear polynomial at give…
oskarth Jun 15, 2023
987359b
refactor: Move Boolean hypercube to separate module
oskarth Jun 15, 2023
5ec547a
feat(ccs): compute_sum_Mz
oskarth Jun 15, 2023
ab70853
fix: Correct SparseMatrix aux methods
CPerezz Jun 21, 2023
d0a5160
fix: Update CCS-related structs & constructor
CPerezz Jun 21, 2023
fa0376e
change: Remove Result from Matrix/vec ops
CPerezz Jun 21, 2023
0559644
change: Update CCS.is_sat() to use iterators
CPerezz Jun 21, 2023
10f1b9f
change: Move matrix padding to SparseMatrix
CPerezz Jun 21, 2023
659ef74
change: Update `from_r1cs` & `pad` CCSShape fns
CPerezz Jun 21, 2023
ff4b44b
change: Standarize `new` methods & remove CAPS
CPerezz Jun 21, 2023
af1e3d5
remove: Unused CCShape::matrix_mul fn
CPerezz Jun 21, 2023
9b2f3c5
fix: Move test-only fns to test mod & fix sparse tests
CPerezz Jun 21, 2023
2a79b9e
change: Remove num_cons & unneeded Results
CPerezz Jun 21, 2023
1b8bba8
fix: Add test feature flag to HyperCube tests mod
CPerezz Jun 21, 2023
8075c15
add: Migrate VirtualPolynomial from espresso/Hyperplonk
CPerezz Jun 22, 2023
b81e432
fix: Include err variant to handle VirtualPolynomial
CPerezz Jun 22, 2023
0f26a23
change: Move CCS stuff to it's own module
CPerezz Jun 22, 2023
68afd5e
add: Migrate VirtualPolynomial from espresso/HyperPlonk
CPerezz Jun 22, 2023
f48dda8
fix: Correct q_computation & update to VirtualPoly usage
CPerezz Jun 22, 2023
71668c7
add: Add CCS -> CCCS conversions
CPerezz Jun 22, 2023
b2e8a6b
change: Update/Improve a bit the HyperCube API
CPerezz Jun 22, 2023
578431c
fix: Include n_cos & n_rows info in SparseMatrix
CPerezz Jun 23, 2023
bc94cb7
change: Update `pad` and `from_r1cs` CCS methods.
CPerezz Jun 23, 2023
dec29ae
remove: coefficients field from HyperCube & refactor
CPerezz Jun 25, 2023
a11cf9c
change: Change HyperCube `evaluate_at` endianness
CPerezz Jun 25, 2023
055751e
add: Port MLE support & tests passing
CPerezz Jun 25, 2023
1450875
fix: Vec * Sparse
CPerezz Jun 25, 2023
a057978
fix: Split Matrix_Vec prod into dense+sparse
CPerezz Jun 25, 2023
1b60494
this code is cursed
CPerezz Jun 26, 2023
827a1ff
lift the curse:
arnaucube Jun 26, 2023
fe14cdd
fix SparseMatrix.to_mle() method, fix test_matrix_to_mle
arnaucube Jun 27, 2023
ce646e6
fix cccs.compute_sum_Mz method, small fix in compute_q, so by consequ…
arnaucube Jun 27, 2023
af45a4c
change: Get M_j_mle from precomputed CCCS M's
CPerezz Jun 27, 2023
c800b39
remove: CCS struct which is unused
CPerezz Jun 27, 2023
b714a20
change: Plce impl blocks under their structs
CPerezz Jun 27, 2023
d436f87
change: Create utils folder under ccs
CPerezz Jun 27, 2023
8e2d99c
change: Move Compute_sum_Mz into util mode
CPerezz Jun 28, 2023
99679fb
change: Move `fix_variables` & util tests to util mod
CPerezz Jun 28, 2023
8b73f47
change: Move CCS test fns as associated fns
CPerezz Jun 28, 2023
eaadaf2
Add: Compute_all_sum_Mz_evals fn to utils module
CPerezz Jun 28, 2023
e3303cd
add: Ck computation & instance satisfaction fns for LCCCS
CPerezz Jun 28, 2023
f5a13a4
remove: Rng param in gen_test_ccs instance fn
CPerezz Jun 28, 2023
bf589be
Add: CCS to LCCCS transformation & v_j s comp
CPerezz Jun 28, 2023
45e8391
add: LCCCS tests with test_lcccs_v_j FAILING
CPerezz Jun 28, 2023
a14d27d
fix: compute_all_sum_Mz_evals with test
CPerezz Jun 29, 2023
3c1017c
fix: test_lcccs_v_j working
CPerezz Jun 29, 2023
2a1fa7a
add: Basic multifolding functions & tests
CPerezz Jun 30, 2023
a2d4408
add: Failing test for sigmas_and_thetas comp
CPerezz Jun 30, 2023
78a4059
fix: test_compute_sigmas_and_thetas
CPerezz Jul 7, 2023
d37afdb
add: Folding functions pending for tests to pass
CPerezz Jul 7, 2023
09cef38
tmp: Check CCCS instance correctness failing
CPerezz Jul 10, 2023
a33e7c3
test(multifolding): Fix CCCS assert
oskarth Jul 11, 2023
216e0e6
refactor(ccs): Remove special CCCSWitness
oskarth Jul 11, 2023
f3bcf68
tests(ccs): Fix test_lcccs_fold test
oskarth Jul 11, 2023
af959fb
chore: fix errors after rebase
oskarth Jul 11, 2023
f07e71b
Merge upstream/main
oskarth Jul 12, 2023
4828a20
chore: fix merge artifact
oskarth Jul 12, 2023
6f1ed22
chore: Restore global warnings and unused
oskarth Jul 12, 2023
20ba46c
refactor: Enable requirement of even public IO by default
oskarth Jul 12, 2023
ca55e73
Hypernova first cut (#15)
CPerezz Jul 12, 2023
cde23f3
refactor(utils): SparseMatrix taking F instead of G (#33)
oskarth Jul 14, 2023
3fdaeab
test(utils): Make generic over Field
oskarth Jul 13, 2023
4ae0cf7
test(utils): Make generic over PrimeField for SparseMatrix
oskarth Jul 14, 2023
3437fae
fix: Address all Clippy suggestions/lints
CPerezz Jul 14, 2023
6fa448f
Update bit_decompose def:
arnaucube Jul 16, 2023
a23c6ab
refactor(virtual_poly): Remove unnecessary default from PhantomData (…
oskarth Jul 20, 2023
9a508b2
refactor: Make tests generic w.r.t. curve/group (#40)
oskarth Jul 20, 2023
1119702
NIMFS-centric API refactor for multifolding impl (#41)
CPerezz Jul 31, 2023
bb7a651
Use polynomial.rs instead of virtual_poly for equality polynomial (#42)
oskarth Aug 2, 2023
0c8915b
Transcript usage inclusion into the codebase (#43)
CPerezz Aug 2, 2023
5f24446
Remove witness from CCCS & LCCCS instances (#48)
CPerezz Aug 4, 2023
a18afe0
Chore/sync upstream (#51)
oskarth Aug 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor: Make tests generic w.r.t. curve/group (#40)
Addresses privacy-scaling-explorations#34

Should cover all files
  • Loading branch information
oskarth authored Jul 20, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit 9a508b2b742cf567cbf6b8daaf11c4e7c032e5be
76 changes: 47 additions & 29 deletions src/ccs/cccs.rs
Original file line number Diff line number Diff line change
@@ -157,17 +157,14 @@ mod tests {
vecs.iter().map(Vec::as_slice).collect()
}

/// Do some sanity checks on q(x). It's a multivariable polynomial and it should evaluate to zero inside the
/// hypercube, but to not-zero outside the hypercube.
#[test]
fn test_compute_q() {
fn test_compute_q_with<G: Group>() {
let mut rng = OsRng;

let z = CCSShape::<Ep>::get_test_z(3);
let (ccs_shape, ccs_witness, ccs_instance) = CCSShape::<Ep>::gen_test_ccs(&z);
let z = CCSShape::<G>::get_test_z(3);
let (ccs_shape, ccs_witness, ccs_instance) = CCSShape::<G>::gen_test_ccs(&z);

// generate ck
let ck = CCSShape::<Ep>::commitment_key(&ccs_shape);
let ck = CCSShape::<G>::commitment_key(&ccs_shape);
// ensure CCS is satisfied
ccs_shape.is_sat(&ck, &ccs_instance, &ccs_witness).unwrap();

@@ -178,30 +175,33 @@ mod tests {

// Evaluate inside the hypercube
BooleanHypercube::new(ccs_shape.s).for_each(|x| {
assert_eq!(Fq::zero(), q.evaluate(&x).unwrap());
assert_eq!(G::Scalar::ZERO, q.evaluate(&x).unwrap());
});

// Evaluate outside the hypercube
let beta: Vec<Fq> = (0..ccs_shape.s).map(|_| Fq::random(&mut rng)).collect();
assert_ne!(Fq::zero(), q.evaluate(&beta).unwrap());
let beta: Vec<G::Scalar> = (0..ccs_shape.s)
.map(|_| G::Scalar::random(&mut rng))
.collect();
assert_ne!(G::Scalar::ZERO, q.evaluate(&beta).unwrap());
}

#[test]
fn test_compute_Q() {
fn test_compute_Q_with<G: Group>() {
let mut rng = OsRng;

let z = CCSShape::<Ep>::get_test_z(3);
let (ccs_shape, ccs_witness, ccs_instance) = CCSShape::<Ep>::gen_test_ccs(&z);
let z = CCSShape::<G>::get_test_z(3);
let (ccs_shape, ccs_witness, ccs_instance) = CCSShape::<G>::gen_test_ccs(&z);

// generate ck
let ck = CCSShape::<Ep>::commitment_key(&ccs_shape);
let ck = CCSShape::<G>::commitment_key(&ccs_shape);
// ensure CCS is satisfied
ccs_shape.is_sat(&ck, &ccs_instance, &ccs_witness).unwrap();

// Generate CCCS artifacts
let cccs_shape = ccs_shape.to_cccs_shape();

let beta: Vec<Fq> = (0..ccs_shape.s).map(|_| Fq::random(&mut rng)).collect();
let beta: Vec<G::Scalar> = (0..ccs_shape.s)
.map(|_| G::Scalar::random(&mut rng))
.collect();

// Compute Q(x) = eq(beta, x) * q(x).
let Q = cccs_shape
@@ -222,22 +222,18 @@ mod tests {
// Now sum Q(x) evaluations in the hypercube and expect it to be 0
let r = BooleanHypercube::new(ccs_shape.s)
.map(|x| Q.evaluate(&x).unwrap())
.fold(Fq::zero(), |acc, result| acc + result);
assert_eq!(r, Fq::zero());
.fold(G::Scalar::ZERO, |acc, result| acc + result);
assert_eq!(r, G::Scalar::ZERO);
}

/// The polynomial G(x) (see above) interpolates q(x) inside the hypercube.
/// Summing Q(x) over the hypercube is equivalent to evaluating G(x) at some point.
/// This test makes sure that G(x) agrees with q(x) inside the hypercube, but not outside
#[test]
fn test_Q_against_q() {
fn test_Q_against_q_with<G: Group>() {
let mut rng = OsRng;

let z = CCSShape::<Ep>::get_test_z(3);
let (ccs_shape, ccs_witness, ccs_instance) = CCSShape::<Ep>::gen_test_ccs(&z);
let z = CCSShape::<G>::get_test_z(3);
let (ccs_shape, ccs_witness, ccs_instance) = CCSShape::<G>::gen_test_ccs(&z);

// generate ck
let ck = CCSShape::<Ep>::commitment_key(&ccs_shape);
let ck = CCSShape::<G>::commitment_key(&ccs_shape);
// ensure CCS is satisfied
ccs_shape.is_sat(&ck, &ccs_instance, &ccs_witness).unwrap();

@@ -257,20 +253,42 @@ mod tests {
// Get G(d) by summing over Q_d(x) over the hypercube
let G_at_d = BooleanHypercube::new(ccs_shape.s)
.map(|x| Q_at_d.evaluate(&x).unwrap())
.fold(Fq::zero(), |acc, result| acc + result);
.fold(G::Scalar::ZERO, |acc, result| acc + result);
assert_eq!(G_at_d, q.evaluate(&d).unwrap());
}

// Now test that they should disagree outside of the hypercube
let r: Vec<Fq> = (0..ccs_shape.s).map(|_| Fq::random(&mut rng)).collect();
let r: Vec<G::Scalar> = (0..ccs_shape.s)
.map(|_| G::Scalar::random(&mut rng))
.collect();
let Q_at_r = cccs_shape
.compute_Q(&z, &r)
.expect("Computing Q_at_r shouldn't fail");

// Get G(d) by summing over Q_d(x) over the hypercube
let G_at_r = BooleanHypercube::new(ccs_shape.s)
.map(|x| Q_at_r.evaluate(&x).unwrap())
.fold(Fq::zero(), |acc, result| acc + result);
.fold(G::Scalar::ZERO, |acc, result| acc + result);
assert_ne!(G_at_r, q.evaluate(&r).unwrap());
}

/// Do some sanity checks on q(x). It's a multivariable polynomial and it should evaluate to zero inside the
/// hypercube, but to not-zero outside the hypercube.
#[test]
fn test_compute_q() {
test_compute_q_with::<Ep>();
}

#[test]
fn test_compute_Q() {
test_compute_Q_with::<Ep>();
}

/// The polynomial G(x) (see above) interpolates q(x) inside the hypercube.
/// Summing Q(x) over the hypercube is equivalent to evaluating G(x) at some point.
/// This test makes sure that G(x) agrees with q(x) inside the hypercube, but not outside
#[test]
fn test_Q_against_q() {
test_Q_against_q_with::<Ep>();
}
}
58 changes: 34 additions & 24 deletions src/ccs/lcccs.rs
Original file line number Diff line number Diff line change
@@ -102,36 +102,33 @@ mod tests {

use super::*;

#[test]
fn satisfied_ccs_is_satisfied_lcccs() {
fn satisfied_ccs_is_satisfied_lcccs_with<G: Group>() {
// Gen test vectors & artifacts
let z = CCSShape::<Ep>::get_test_z(3);
let (ccs, witness, instance) = CCSShape::<Ep>::gen_test_ccs(&z);
let z = CCSShape::<G>::get_test_z(3);
let (ccs, witness, instance) = CCSShape::<G>::gen_test_ccs(&z);
let ck = ccs.commitment_key();
assert!(ccs.is_sat(&ck, &instance, &witness).is_ok());

// Wrong z so that the relation does not hold
let mut bad_z = z.clone();
bad_z[3] = Fq::ZERO;

// LCCCS with the correct z should pass
let (lcccs, _) = ccs.to_lcccs(&mut OsRng, &ck, &z);
assert!(lcccs.is_sat(&ck, &witness).is_ok());

// Wrong z so that the relation does not hold
let mut bad_z = z;
bad_z[3] = G::Scalar::ZERO;

// LCCCS with the wrong z should not pass `is_sat`.
// LCCCS with the correct z should pass
let (lcccs, _) = ccs.to_lcccs(&mut OsRng, &ck, &bad_z);
assert!(lcccs.is_sat(&ck, &witness).is_err());
}

#[test]
/// Test linearized CCCS v_j against the L_j(x)
fn test_lcccs_v_j() {
fn test_lcccs_v_j_with<G: Group>() {
let mut rng = OsRng;

// Gen test vectors & artifacts
let z = CCSShape::<Ep>::get_test_z(3);
let (ccs, _, _) = CCSShape::<Ep>::gen_test_ccs(&z);
let z = CCSShape::<G>::get_test_z(3);
let (ccs, _, _) = CCSShape::<G>::gen_test_ccs(&z);
let ck = ccs.commitment_key();

// Get LCCCS
@@ -143,29 +140,25 @@ mod tests {
for (v_i, L_j_x) in lcccs.v.into_iter().zip(vec_L_j_x) {
let sum_L_j_x = BooleanHypercube::new(ccs.s)
.map(|y| L_j_x.evaluate(&y).unwrap())
.fold(Fq::ZERO, |acc, result| acc + result);
.fold(G::Scalar::ZERO, |acc, result| acc + result);
assert_eq!(v_i, sum_L_j_x);
}
}

/// Given a bad z, check that the v_j should not match with the L_j(x)
#[test]
fn test_bad_v_j() {
fn test_bad_v_j_with<G: Group>() {
let mut rng = OsRng;

// Gen test vectors & artifacts
let z = CCSShape::<Ep>::get_test_z(3);
let (ccs, witness, instance) = CCSShape::<Ep>::gen_test_ccs(&z);
let z = CCSShape::<G>::get_test_z(3);
let (ccs, _, _) = CCSShape::<G>::gen_test_ccs(&z);
let ck = ccs.commitment_key();

// Mutate z so that the relation does not hold
let mut bad_z = z.clone();
bad_z[3] = Fq::ZERO;
bad_z[3] = G::Scalar::ZERO;

// Compute v_j with the right z
// Get LCCCS
let (lcccs, _) = ccs.to_lcccs(&mut rng, &ck, &z);
// Assert LCCCS is satisfied with the original Z
assert!(lcccs.is_sat(&ck, &witness).is_ok());

// Bad compute L_j(x) with the bad z
let vec_L_j_x = lcccs.compute_Ls(&bad_z);
@@ -179,12 +172,29 @@ mod tests {
for (v_i, L_j_x) in lcccs.v.into_iter().zip(vec_L_j_x) {
let sum_L_j_x = BooleanHypercube::new(ccs.s)
.map(|y| L_j_x.evaluate(&y).unwrap())
.fold(Fq::ZERO, |acc, result| acc + result);
.fold(G::Scalar::ZERO, |acc, result| acc + result);
if v_i != sum_L_j_x {
satisfied = false;
}
}

assert!(!satisfied);
}

#[test]
fn satisfied_ccs_is_satisfied_lcccs() {
satisfied_ccs_is_satisfied_lcccs_with::<Ep>();
}

#[test]
/// Test linearized CCCS v_j against the L_j(x)
fn test_lcccs_v_j() {
test_lcccs_v_j_with::<Ep>();
}

/// Given a bad z, check that the v_j should not match with the L_j(x)
#[test]
fn test_bad_v_j() {
test_bad_v_j_with::<Ep>();
}
}
26 changes: 14 additions & 12 deletions src/ccs/mod.rs
Original file line number Diff line number Diff line change
@@ -399,16 +399,14 @@ pub mod test {
use ff::{Field, PrimeField};
use rand::rngs::OsRng;

type S = pasta_curves::pallas::Scalar;
type G = pasta_curves::pallas::Point;
use pasta_curves::Ep;

#[test]
fn test_tiny_ccs() {
fn test_tiny_ccs_with<G: Group>() {
// 1. Generate valid R1CS Shape
// 2. Convert to CCS
// 3. Test that it is satisfiable

let one = S::one();
let one = G::Scalar::ONE;
let (num_cons, num_vars, num_io, A, B, C) = {
let num_cons = 4;
let num_vars = 4;
@@ -425,9 +423,9 @@ pub mod test {
// constraint and a column for every entry in z = (vars, u, inputs)
// An R1CS instance is satisfiable iff:
// Az \circ Bz = u \cdot Cz + E, where z = (vars, 1, inputs)
let mut A: Vec<(usize, usize, S)> = Vec::new();
let mut B: Vec<(usize, usize, S)> = Vec::new();
let mut C: Vec<(usize, usize, S)> = Vec::new();
let mut A: Vec<(usize, usize, G::Scalar)> = Vec::new();
let mut B: Vec<(usize, usize, G::Scalar)> = Vec::new();
let mut C: Vec<(usize, usize, G::Scalar)> = Vec::new();

// constraint 0 entries in (A,B,C)
// `I0 * I0 - Z0 = 0`
@@ -470,12 +468,11 @@ pub mod test {

// generate generators and ro constants
let _ck = S.commitment_key();
let _ro_consts =
<<G as Group>::RO as ROTrait<<G as Group>::Base, <G as Group>::Scalar>>::Constants::new();
let _ro_consts = <G::RO as ROTrait<G::Base, G::Scalar>>::Constants::new();

// 3. Test that CCS is satisfiable
let _rand_inst_witness_generator =
|ck: &CommitmentKey<G>, I: &S| -> (S, CCSInstance<G>, CCSWitness<G>) {
|ck: &CommitmentKey<G>, I: &G::Scalar| -> (G::Scalar, CCSInstance<G>, CCSWitness<G>) {
let i0 = *I;

// compute a satisfying (vars, X) tuple
@@ -486,7 +483,7 @@ pub mod test {
let i1 = z2 + one + one + one + one + one; // constraint 3

// store the witness and IO for the instance
let W = vec![z0, z1, z2, S::zero()];
let W = vec![z0, z1, z2, G::Scalar::ZERO];
let X = vec![i0, i1];
(i1, W, X)
};
@@ -506,4 +503,9 @@ pub mod test {
(O, U, ccs_w)
};
}

#[test]
fn test_tiny_ccs() {
test_tiny_ccs_with::<Ep>();
}
}
81 changes: 47 additions & 34 deletions src/ccs/multifolding.rs
Original file line number Diff line number Diff line change
@@ -205,48 +205,48 @@ pub fn eq_eval<F: PrimeField>(x: &[F], y: &[F]) -> F {
#[cfg(test)]
mod tests {
use super::*;
use crate::ccs::util::virtual_poly::build_eq_x_r;
use crate::ccs::{test, util::virtual_poly::build_eq_x_r};
use pasta_curves::{Ep, Fq};
use rand_core::OsRng;
// NIMFS: Non Interactive Multifolding Scheme
type NIMFS = Multifolding<Ep>;
type NIMFS<G> = Multifolding<G>;

#[test]
fn test_compute_g() {
let z1 = CCSShape::<Ep>::get_test_z(3);
let z2 = CCSShape::<Ep>::get_test_z(4);
fn test_compute_g_with<G: Group>() {
let z1 = CCSShape::<G>::get_test_z(3);
let z2 = CCSShape::<G>::get_test_z(4);

let (_, ccs_witness_1, ccs_instance_1) = CCSShape::gen_test_ccs(&z2);
let (ccs, ccs_witness_2, ccs_instance_2) = CCSShape::gen_test_ccs(&z1);
let (_, ccs_witness_1, ccs_instance_1) = CCSShape::<G>::gen_test_ccs(&z2);
let (ccs, ccs_witness_2, ccs_instance_2) = CCSShape::<G>::gen_test_ccs(&z1);
let ck = ccs.commitment_key();

assert!(ccs.is_sat(&ck, &ccs_instance_1, &ccs_witness_1).is_ok());
assert!(ccs.is_sat(&ck, &ccs_instance_2, &ccs_witness_2).is_ok());

let mut rng = OsRng;
let gamma: Fq = Fq::random(&mut rng);
let beta: Vec<Fq> = (0..ccs.s).map(|_| Fq::random(&mut rng)).collect();
let gamma: G::Scalar = G::Scalar::random(&mut rng);
let beta: Vec<G::Scalar> = (0..ccs.s).map(|_| G::Scalar::random(&mut rng)).collect();

let (lcccs_instance, _) = ccs.to_lcccs(&mut rng, &ck, &z1);
let cccs_instance = ccs.to_cccs_shape();

let mut sum_v_j_gamma = Fq::zero();
let mut sum_v_j_gamma = G::Scalar::ZERO;
for j in 0..lcccs_instance.v.len() {
let gamma_j = gamma.pow([j as u64]);
sum_v_j_gamma += lcccs_instance.v[j] * gamma_j;
}

// Compute g(x) with that r_x

let g = NIMFS::compute_g(&lcccs_instance, &cccs_instance, &z1, &z2, gamma, &beta);

// evaluate g(x) over x \in {0,1}^s
let mut g_on_bhc = Fq::zero();
let mut g_on_bhc = G::Scalar::ZERO;
for x in BooleanHypercube::new(ccs.s) {
g_on_bhc += g.evaluate(&x).unwrap();
}

// evaluate sum_{j \in [t]} (gamma^j * Lj(x)) over x \in {0,1}^s
let mut sum_Lj_on_bhc = Fq::zero();
let mut sum_Lj_on_bhc = G::Scalar::ZERO;
let vec_L = lcccs_instance.compute_Ls(&z1);
for x in BooleanHypercube::new(ccs.s) {
for (j, coeff) in vec_L.iter().enumerate() {
@@ -256,7 +256,7 @@ mod tests {
}

// Q(x) over bhc is assumed to be zero, as checked in the test 'test_compute_Q'
assert_ne!(g_on_bhc, Fq::zero());
assert_ne!(g_on_bhc, G::Scalar::ZERO);

// evaluating g(x) over the boolean hypercube should give the same result as evaluating the
// sum of gamma^j * Lj(x) over the boolean hypercube
@@ -267,22 +267,21 @@ mod tests {
assert_eq!(g_on_bhc, sum_v_j_gamma);
}

#[test]
fn test_compute_sigmas_and_thetas() {
let z1 = CCSShape::<Ep>::get_test_z(3);
let z2 = CCSShape::<Ep>::get_test_z(4);
fn test_compute_sigmas_and_thetas_with<G: Group>() {
let z1 = CCSShape::<G>::get_test_z(3);
let z2 = CCSShape::<G>::get_test_z(4);

let (_, ccs_witness_1, ccs_instance_1) = CCSShape::gen_test_ccs(&z2);
let (ccs, ccs_witness_2, ccs_instance_2) = CCSShape::gen_test_ccs(&z1);
let ck = ccs.commitment_key();
let (_, ccs_witness_1, ccs_instance_1) = CCSShape::<G>::gen_test_ccs(&z2);
let (ccs, ccs_witness_2, ccs_instance_2) = CCSShape::<G>::gen_test_ccs(&z1);
let ck: CommitmentKey<G> = ccs.commitment_key();

assert!(ccs.is_sat(&ck, &ccs_instance_1, &ccs_witness_1).is_ok());
assert!(ccs.is_sat(&ck, &ccs_instance_2, &ccs_witness_2).is_ok());

let mut rng = OsRng;
let gamma: Fq = Fq::random(&mut rng);
let beta: Vec<Fq> = (0..ccs.s).map(|_| Fq::random(&mut rng)).collect();
let r_x_prime: Vec<Fq> = (0..ccs.s).map(|_| Fq::random(&mut rng)).collect();
let gamma: G::Scalar = G::Scalar::random(&mut rng);
let beta: Vec<G::Scalar> = (0..ccs.s).map(|_| G::Scalar::random(&mut rng)).collect();
let r_x_prime: Vec<G::Scalar> = (0..ccs.s).map(|_| G::Scalar::random(&mut rng)).collect();

// Initialize a multifolding object
let (lcccs_instance, _) = ccs.to_lcccs(&mut rng, &ck, &z1);
@@ -298,12 +297,12 @@ mod tests {
// Assert `g` is correctly computed here.
{
// evaluate g(x) over x \in {0,1}^s
let mut g_on_bhc = Fq::zero();
let mut g_on_bhc = G::Scalar::ZERO;
for x in BooleanHypercube::new(ccs.s) {
g_on_bhc += g.evaluate(&x).unwrap();
}
// evaluate sum_{j \in [t]} (gamma^j * Lj(x)) over x \in {0,1}^s
let mut sum_Lj_on_bhc = Fq::zero();
let mut sum_Lj_on_bhc = G::Scalar::ZERO;
let vec_L = lcccs_instance.compute_Ls(&z1);
for x in BooleanHypercube::new(ccs.s) {
for (j, coeff) in vec_L.iter().enumerate() {
@@ -339,20 +338,24 @@ mod tests {
}

#[test]
fn test_lcccs_fold() {
let z1 = CCSShape::<Ep>::get_test_z(3);
let z2 = CCSShape::<Ep>::get_test_z(4);
fn test_compute_g() {
test_compute_g_with::<Ep>();
}

fn test_lccs_fold_with<G: Group>() {
let z1 = CCSShape::<G>::get_test_z(3);
let z2 = CCSShape::<G>::get_test_z(4);

// ccs stays the same regardless of z1 or z2
let (ccs, ccs_witness_1, ccs_instance_1) = CCSShape::gen_test_ccs(&z1);
let (_, ccs_witness_2, ccs_instance_2) = CCSShape::gen_test_ccs(&z2);
let ck = ccs.commitment_key();
let (ccs, ccs_witness_1, ccs_instance_1) = CCSShape::<G>::gen_test_ccs(&z1);
let (_, ccs_witness_2, ccs_instance_2) = CCSShape::<G>::gen_test_ccs(&z2);
let ck: CommitmentKey<G> = ccs.commitment_key();

assert!(ccs.is_sat(&ck, &ccs_instance_1, &ccs_witness_1).is_ok());
assert!(ccs.is_sat(&ck, &ccs_instance_2, &ccs_witness_2).is_ok());

let mut rng = OsRng;
let r_x_prime: Vec<Fq> = (0..ccs.s).map(|_| Fq::random(&mut rng)).collect();
let r_x_prime: Vec<G::Scalar> = (0..ccs.s).map(|_| G::Scalar::random(&mut rng)).collect();

// Generate a new multifolding instance
let mut nimfs = NIMFS::new(ccs.clone());
@@ -370,7 +373,7 @@ mod tests {
.is_sat(&ck, &ccs_witness_2, &cccs_instance)
.is_ok());

let rho = Fq::random(&mut rng);
let rho = G::Scalar::random(&mut rng);

let folded = nimfs.fold(
&lcccs_instance,
@@ -386,4 +389,14 @@ mod tests {
// check lcccs relation
assert!(folded.is_sat(&ck, &w_folded).is_ok());
}

#[test]
fn test_compute_sigmas_and_thetas() {
test_compute_sigmas_and_thetas_with::<Ep>()
}

#[test]
fn test_lcccs_fold() {
test_lccs_fold_with::<Ep>()
}
}
109 changes: 68 additions & 41 deletions src/ccs/util/mod.rs
Original file line number Diff line number Diff line change
@@ -117,40 +117,39 @@ mod tests {
use pasta_curves::{Ep, Fq};
use rand_core::OsRng;

#[test]
fn test_fix_variables() {
let A = SparseMatrix::<Fq>::with_coeffs(
fn test_fix_variables_with<F: PrimeField>() {
let A = SparseMatrix::<F>::with_coeffs(
4,
4,
vec![
(0, 0, Fq::from(2u64)),
(0, 1, Fq::from(3u64)),
(0, 2, Fq::from(4u64)),
(0, 3, Fq::from(4u64)),
(1, 0, Fq::from(4u64)),
(1, 1, Fq::from(11u64)),
(1, 2, Fq::from(14u64)),
(1, 3, Fq::from(14u64)),
(2, 0, Fq::from(2u64)),
(2, 1, Fq::from(8u64)),
(2, 2, Fq::from(17u64)),
(2, 3, Fq::from(17u64)),
(3, 0, Fq::from(420u64)),
(3, 1, Fq::from(4u64)),
(3, 2, Fq::from(2u64)),
(3, 3, Fq::ZERO),
(0, 0, F::from(2u64)),
(0, 1, F::from(3u64)),
(0, 2, F::from(4u64)),
(0, 3, F::from(4u64)),
(1, 0, F::from(4u64)),
(1, 1, F::from(11u64)),
(1, 2, F::from(14u64)),
(1, 3, F::from(14u64)),
(2, 0, F::from(2u64)),
(2, 1, F::from(8u64)),
(2, 2, F::from(17u64)),
(2, 3, F::from(17u64)),
(3, 0, F::from(420u64)),
(3, 1, F::from(4u64)),
(3, 2, F::from(2u64)),
(3, 3, F::ZERO),
],
);

let A_mle = A.to_mle();
let bhc = BooleanHypercube::<Fq>::new(2);
let bhc = BooleanHypercube::<F>::new(2);
for (i, y) in bhc.enumerate() {
let A_mle_op = fix_variables(&A_mle, &y);

// Check that fixing first variables pins down a column
// i.e. fixing x to 0 will return the first column
// fixing x to 1 will return the second column etc.
let column_i: Vec<Fq> = A
let column_i: Vec<F> = A
.clone()
.coeffs()
.iter()
@@ -163,7 +162,7 @@ mod tests {
// // Now check that fixing last variables pins down a row
// // i.e. fixing y to 0 will return the first row
// // fixing y to 1 will return the second row etc.
let row_i: Vec<Fq> = A
let row_i: Vec<F> = A
.clone()
.coeffs()
.iter()
@@ -181,50 +180,78 @@ mod tests {
}
}

#[test]
fn test_compute_sum_Mz_over_boolean_hypercube() {
let z = CCSShape::<Ep>::get_test_z(3);
let (ccs, _, _) = CCSShape::<Ep>::gen_test_ccs(&z);
fn test_compute_sum_Mz_over_boolean_hypercube_with<G: Group>() {
let z = CCSShape::<G>::get_test_z(3);
let (ccs, _, _) = CCSShape::<G>::gen_test_ccs(&z);

// Generate other artifacts
let ck = CCSShape::<Ep>::commitment_key(&ccs);
let ck = CCSShape::<G>::commitment_key(&ccs);
let (_, _, cccs) = ccs.to_cccs(&mut OsRng, &ck, &z);

let z_mle = dense_vec_to_mle(ccs.s_prime, &z);

// check that evaluating over all the values x over the boolean hypercube, the result of
// the next for loop is equal to 0
let mut r = Fq::zero();
let mut r = G::Scalar::ZERO;
let bch = BooleanHypercube::new(ccs.s);
for x in bch.into_iter() {
for i in 0..ccs.q {
let mut Sj_prod = Fq::one();
let mut Sj_prod = G::Scalar::ONE;
for j in ccs.S[i].clone() {
let sum_Mz: MultilinearPolynomial<Fq> = compute_sum_Mz::<Ep>(&cccs.M_MLE[j], &z_mle);
let sum_Mz: MultilinearPolynomial<G::Scalar> =
compute_sum_Mz::<G>(&cccs.M_MLE[j], &z_mle);
let sum_Mz_x = sum_Mz.evaluate(&x);
Sj_prod *= sum_Mz_x;
}
r += Sj_prod * ccs.c[i];
}
assert_eq!(r, Fq::ZERO);
assert_eq!(r, G::Scalar::ZERO);
}
}

#[test]
fn test_compute_all_sum_Mz_evals() {
let z = CCSShape::<Ep>::get_test_z(3);
let (ccs, _, _) = CCSShape::<Ep>::gen_test_ccs(&z);
fn test_compute_all_sum_Mz_evals_with<G: Group>() {
let z = CCSShape::<G>::get_test_z(3);
let (ccs, _, _) = CCSShape::<G>::gen_test_ccs(&z);

// Generate other artifacts
let ck = CCSShape::<Ep>::commitment_key(&ccs);
let ck = CCSShape::<G>::commitment_key(&ccs);
let (_, _, cccs) = ccs.to_cccs(&mut OsRng, &ck, &z);

let mut r = vec![Fq::ONE, Fq::ZERO];
let res = compute_all_sum_Mz_evals::<Ep>(cccs.M_MLE.as_slice(), &z, &r, ccs.s_prime);
assert_eq!(res, vec![Fq::from(9u64), Fq::from(3u64), Fq::from(27u64)]);
let mut r = vec![G::Scalar::ONE, G::Scalar::ZERO];
let res = compute_all_sum_Mz_evals::<G>(cccs.M_MLE.as_slice(), &z, &r, ccs.s_prime);
assert_eq!(
res,
vec![
G::Scalar::from(9u64),
G::Scalar::from(3u64),
G::Scalar::from(27u64)
]
);

r.reverse();
let res = compute_all_sum_Mz_evals::<Ep>(cccs.M_MLE.as_slice(), &z, &r, ccs.s_prime);
assert_eq!(res, vec![Fq::from(30u64), Fq::from(1u64), Fq::from(30u64)])
let res = compute_all_sum_Mz_evals::<G>(cccs.M_MLE.as_slice(), &z, &r, ccs.s_prime);
assert_eq!(
res,
vec![
G::Scalar::from(30u64),
G::Scalar::from(1u64),
G::Scalar::from(30u64)
]
)
}

#[test]
fn test_fix_variables() {
test_fix_variables_with::<Fq>();
}

#[test]
fn test_compute_sum_Mz_over_boolean_hypercube() {
test_compute_sum_Mz_over_boolean_hypercube_with::<Ep>();
}

#[test]
fn test_compute_all_sum_Mz_evals() {
test_compute_all_sum_Mz_evals_with::<Ep>();
}
}
13 changes: 8 additions & 5 deletions src/hypercube.rs
Original file line number Diff line number Diff line change
@@ -64,13 +64,16 @@ mod tests {
use ff::Field;
use pasta_curves::Fq;

#[test]
fn test_evaluate() {
// Declare the coefficients in the order 1, x, y, xy, z, xz, yz, xyz.
let poly = BooleanHypercube::<Fq>::new(3);
fn test_evaluate_with<F: PrimeField>() {
let poly = BooleanHypercube::<F>::new(3);

let point = 7usize;
// So, f(1, 1, 1) = 5.
assert_eq!(poly.evaluate_at(point), vec![Fq::ONE, Fq::ONE, Fq::ONE]);
assert_eq!(poly.evaluate_at(point), vec![F::ONE, F::ONE, F::ONE]);
}

#[test]
fn test_evaluate() {
test_evaluate_with::<Fq>();
}
}
114 changes: 69 additions & 45 deletions src/spartan/polynomial.rs
Original file line number Diff line number Diff line change
@@ -253,101 +253,125 @@ mod tests {
use super::*;
use pasta_curves::Fp;

fn make_mlp(len: usize, value: Fp) -> MultilinearPolynomial<Fp> {
fn make_mlp<F: PrimeField>(len: usize, value: F) -> MultilinearPolynomial<F> {
MultilinearPolynomial {
num_vars: len.count_ones() as usize,
Z: vec![value; len],
}
}

#[test]
fn test_eq_polynomial() {
let eq_poly = EqPolynomial::<Fp>::new(vec![Fp::one(), Fp::zero(), Fp::one()]);
let y = eq_poly.evaluate(vec![Fp::one(), Fp::one(), Fp::one()].as_slice());
assert_eq!(y, Fp::zero());
fn test_eq_polynomial_with<F: PrimeField>() {
let eq_poly = EqPolynomial::<F>::new(vec![F::ONE, F::ZERO, F::ONE]);
let y = eq_poly.evaluate(vec![F::ONE, F::ONE, F::ONE].as_slice());
assert_eq!(y, F::ZERO);

let y = eq_poly.evaluate(vec![Fp::one(), Fp::zero(), Fp::one()].as_slice());
assert_eq!(y, Fp::one());
let y = eq_poly.evaluate(vec![F::ONE, F::ZERO, F::ONE].as_slice());
assert_eq!(y, F::ONE);

let eval_list = eq_poly.evals();
for (i, &coeff) in eval_list.iter().enumerate().take((2_usize).pow(3)) {
if i == 5 {
assert_eq!(coeff, Fp::one());
assert_eq!(coeff, F::ONE);
} else {
assert_eq!(coeff, Fp::zero());
assert_eq!(coeff, F::ZERO);
}
}
}

#[test]
fn test_multilinear_polynomial() {
fn test_multilinear_polynomial_with<F: PrimeField>() {
// Let the polynomial has 3 variables, p(x_1, x_2, x_3) = (x_1 + x_2) * x_3
// Evaluations of the polynomial at boolean cube are [0, 0, 0, 1, 0, 1, 0, 2].

let TWO = Fp::from(2);
let TWO = F::from(2);

let Z = vec![
Fp::zero(),
Fp::zero(),
Fp::zero(),
Fp::one(),
Fp::zero(),
Fp::one(),
Fp::zero(),
F::ZERO,
F::ZERO,
F::ZERO,
F::ONE,
F::ZERO,
F::ONE,
F::ZERO,
TWO,
];
let m_poly = MultilinearPolynomial::<Fp>::new(Z.clone());
let m_poly = MultilinearPolynomial::<F>::new(Z.clone());
assert_eq!(m_poly.get_num_vars(), 3);

let x = vec![Fp::one(), Fp::one(), Fp::one()];
let x = vec![F::ONE, F::ONE, F::ONE];
assert_eq!(m_poly.evaluate(x.as_slice()), TWO);

let y = MultilinearPolynomial::<Fp>::evaluate_with(Z.as_slice(), x.as_slice());
let y = MultilinearPolynomial::<F>::evaluate_with(Z.as_slice(), x.as_slice());
assert_eq!(y, TWO);
}

#[test]
fn test_sparse_polynomial() {
fn test_sparse_polynomial_with<F: PrimeField>() {
// Let the polynomial has 3 variables, p(x_1, x_2, x_3) = (x_1 + x_2) * x_3
// Evaluations of the polynomial at boolean cube are [0, 0, 0, 1, 0, 1, 0, 2].

let TWO = Fp::from(2);
let Z = vec![(3, Fp::one()), (5, Fp::one()), (7, TWO)];
let m_poly = SparsePolynomial::<Fp>::new(3, Z);
let TWO = F::from(2);
let Z = vec![(3, F::ONE), (5, F::ONE), (7, TWO)];
let m_poly = SparsePolynomial::<F>::new(3, Z);

let x = vec![Fp::one(), Fp::one(), Fp::one()];
let x = vec![F::ONE, F::ONE, F::ONE];
assert_eq!(m_poly.evaluate(x.as_slice()), TWO);

let x = vec![Fp::one(), Fp::zero(), Fp::one()];
assert_eq!(m_poly.evaluate(x.as_slice()), Fp::one());
let x = vec![F::ONE, F::ZERO, F::ONE];
assert_eq!(m_poly.evaluate(x.as_slice()), F::ONE);
}

#[test]
fn test_mlp_add() {
let mlp1 = make_mlp(4, Fp::from(3));
let mlp2 = make_mlp(4, Fp::from(7));
fn test_eq_polynomial() {
test_eq_polynomial_with::<Fp>();
}

#[test]
fn test_multilinear_polynomial() {
test_multilinear_polynomial_with::<Fp>();
}

#[test]
fn test_sparse_polynomial() {
test_sparse_polynomial_with::<Fp>();
}

fn test_mlp_add_with<F: PrimeField>() {
let mlp1 = make_mlp(4, F::from(3));
let mlp2 = make_mlp(4, F::from(7));

let mlp3 = mlp1.add(mlp2).unwrap();

assert_eq!(mlp3.Z, vec![Fp::from(10); 4]);
assert_eq!(mlp3.Z, vec![F::from(10); 4]);
}

#[test]
fn test_mlp_scalar_mul() {
let mlp = make_mlp(4, Fp::from(3));
fn test_mlp_scalar_mul_with<F: PrimeField>() {
let mlp = make_mlp(4, F::from(3));

let mlp2 = mlp.scalar_mul(&Fp::from(2));
let mlp2 = mlp.scalar_mul(&F::from(2));

assert_eq!(mlp2.Z, vec![Fp::from(6); 4]);
assert_eq!(mlp2.Z, vec![F::from(6); 4]);
}

#[test]
fn test_mlp_mul() {
let mlp1 = make_mlp(4, Fp::from(3));
let mlp2 = make_mlp(4, Fp::from(7));
fn test_mlp_mul_with<F: PrimeField>() {
let mlp1 = make_mlp(4, F::from(3));
let mlp2 = make_mlp(4, F::from(7));

let mlp3 = mlp1.mul(mlp2).unwrap();

assert_eq!(mlp3.Z, vec![Fp::from(21); 4]);
assert_eq!(mlp3.Z, vec![F::from(21); 4]);
}

#[test]
fn test_mlp_add() {
test_mlp_add_with::<Fp>();
}

#[test]
fn test_mlp_scalar_mul() {
test_mlp_scalar_mul_with::<Fp>();
}

#[test]
fn test_mlp_mul() {
test_mlp_mul_with::<Fp>();
}
}