From 412952686dc5408110636b82742903671943ab3e Mon Sep 17 00:00:00 2001 From: vanhger Date: Fri, 10 May 2024 08:42:12 +0700 Subject: [PATCH 01/18] Add ScalarField def and vector commitment feature. --- kzg/src/types.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/kzg/src/types.rs b/kzg/src/types.rs index 829ff04..3f98058 100644 --- a/kzg/src/types.rs +++ b/kzg/src/types.rs @@ -5,4 +5,5 @@ use ark_poly::univariate::DensePolynomial; pub type G1Point = as Pairing>::G1Affine; pub type G2Point = as Pairing>::G2Affine; +pub type ScalarField = as Pairing>::ScalarField; pub type Poly = DensePolynomial; From 7188a8856367127317cd7b440eb7c7762c134f4d Mon Sep 17 00:00:00 2001 From: vanhger Date: Fri, 10 May 2024 08:48:10 +0700 Subject: [PATCH 02/18] Create Nova Folding Scheme lib --- nova/Cargo.toml | 15 +++++++++++++++ nova/src/lib.rs | 0 2 files changed, 15 insertions(+) create mode 100644 nova/Cargo.toml create mode 100644 nova/src/lib.rs diff --git a/nova/Cargo.toml b/nova/Cargo.toml new file mode 100644 index 0000000..59283d4 --- /dev/null +++ b/nova/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "nova" +version = "0.1.0" +edition = "2021" + +[dependencies] +ark-ff = "0.4.2" +ark-ec = "0.4.2" +ark-bls12-381 = "0.4.0" +ark-serialize = "0.4.2" +ark-std = "0.4.0" +rand = "0.8.5" +sha2 = "0.10" +kzg = { path = "../kzg" } +plonk = {path = "../plonk"} \ No newline at end of file diff --git a/nova/src/lib.rs b/nova/src/lib.rs new file mode 100644 index 0000000..e69de29 From bcdbc4a82c1445d5fddaaf4772d5a25544b3d2e6 Mon Sep 17 00:00:00 2001 From: vanhger Date: Fri, 10 May 2024 08:50:50 +0700 Subject: [PATCH 03/18] Create Fiat-Shamir Transcript and Matrix operator functions --- nova/src/lib.rs | 2 + nova/src/transcript.rs | 206 +++++++++++++++++++++++++++++++++++++++++ nova/src/utils.rs | 64 +++++++++++++ 3 files changed, 272 insertions(+) create mode 100644 nova/src/transcript.rs create mode 100644 nova/src/utils.rs diff --git a/nova/src/lib.rs b/nova/src/lib.rs index e69de29..64ddcae 100644 --- a/nova/src/lib.rs +++ b/nova/src/lib.rs @@ -0,0 +1,2 @@ +mod utils; +mod transcript; \ No newline at end of file diff --git a/nova/src/transcript.rs b/nova/src/transcript.rs new file mode 100644 index 0000000..7654315 --- /dev/null +++ b/nova/src/transcript.rs @@ -0,0 +1,206 @@ +use std::marker::PhantomData; + +use ark_bls12_381::Fr; +use ark_ff::{PrimeField, UniformRand}; +use ark_serialize::{CanonicalSerialize, Write}; +use rand::rngs::StdRng; +use rand::SeedableRng; +use sha2::Digest; + +use kzg::commitment::KzgCommitment; +use kzg::types::ScalarField; + +/// Generates Fiat-Shamir challenges for the KZG scheme. +/// +/// The `Transcript` struct is responsible for generating challenges. +#[derive(Clone, Default)] +pub struct Transcript { + data: Option>, + generated: bool, + + #[allow(dead_code)] + /// Phantom data for annotation purposes. + _phantom_data_t: PhantomData, +} + +impl Transcript { + /// Creates a new `Transcript` instance from a list of commitments. + /// + /// # Parameters + /// + /// - `kzg_commitment`: A slice containing the commitments. + /// + /// # Returns + /// + /// A new `Transcript` instance. + pub fn from_commitment(kzg_commitment: &[KzgCommitment]) -> Self { + let mut challenge_parse = Self::default(); + for commitment in kzg_commitment { + challenge_parse.feed(commitment); + } + challenge_parse + } + + pub fn from_scalar_number(numbers: &[ScalarField]) -> Self { + let mut challenge_parse = Self::default(); + for number in numbers { + challenge_parse.feed_scalar_num(*number); + } + challenge_parse + } +} + +impl Transcript { + /// Feeds a commitment to the transcript. + /// + /// # Parameters + /// + /// - `kzg_commitment`: The commitment to feed to the generator. + pub fn feed(&mut self, kzg_commitment: &KzgCommitment) { + let mut hasher = T::default(); + hasher.update(self.data.take().unwrap_or_default()); + kzg_commitment + .inner() + .serialize_uncompressed(HashMarshaller(&mut hasher)) + .expect("HashMarshaller::flush should be infallible!"); + self.data = Some(hasher.finalize().to_vec()); + self.generated = false; + } + + /// Feeds a number to the transcript + pub fn feed_scalar_num(&mut self, num: ScalarField) { + let mut hasher = T::default(); + hasher.update(self.data.take().unwrap_or_default()); + num + .serialize_uncompressed(HashMarshaller(&mut hasher)) + .expect("HashMarshaller::flush should be infallible!"); + self.data = Some(hasher.finalize().to_vec()); + self.generated = false; + } + + fn generate_rng_with_seed(&mut self) -> StdRng { + if self.generated { + panic!("I'm hungry! Feed me something first"); + } + self.generated = true; + let mut seed: [u8; 8] = Default::default(); + seed.copy_from_slice(&self.data.clone().unwrap_or_default()[0..8]); + let seed = u64::from_le_bytes(seed); + StdRng::seed_from_u64(seed) + } + + /// Generates challenges of a specified length. + /// + /// # Parameters + /// + /// - `N`: The length of the challenges to generate. + /// + /// # Returns + /// + /// An array of generated challenges. + pub fn generate_challenges(&mut self) -> [Fr; N] { + let mut rng = self.generate_rng_with_seed(); + let points = [0; N]; + points.map(|_| Fr::rand(&mut rng)) + } +} + +// This private struct works around Serialize taking the pre-existing +// std::io::Write instance of most digest::Digest implementations by value +struct HashMarshaller<'a, H: Digest>(&'a mut H); + +impl<'a, H: Digest> Write for HashMarshaller<'a, H> { + #[inline] + fn write(&mut self, buf: &[u8]) -> ark_std::io::Result { + Digest::update(self.0, buf); + Ok(buf.len()) + } + + #[inline] + fn flush(&mut self) -> ark_std::io::Result<()> { + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use std::ops::Mul; + + use ark_ec::{AffineRepr, CurveGroup}; + use sha2::Sha256; + use kzg::types::G1Point; + use ark_bls12_381::Fr; + + + use super::*; + + #[test] + fn aggregation_digest_test() { + let commitment1 = KzgCommitment(G1Point::generator().mul(Fr::from(1)).into_affine()); + let commitment2 = KzgCommitment(G1Point::generator().mul(Fr::from(2)).into_affine()); + let commitments1: [KzgCommitment; 2] = [commitment1.clone(), commitment2.clone()]; + let [a, aa, aaa] = + Transcript::::from_commitment(&commitments1).generate_challenges(); + + let commitments2: [KzgCommitment; 1] = [commitment2.clone()]; + let [b] = + Transcript::::from_commitment(&commitments2).generate_challenges(); + assert_ne!(a, b, "should be different"); + + let commitments3: [KzgCommitment; 2] = [commitment1.clone(), commitment2.clone()]; + let [c, cc, ccc] = + Transcript::::from_commitment(&commitments3).generate_challenges(); + assert_eq!(a, c, "should be equal"); + assert_eq!(aa, cc, "should be equal"); + assert_eq!(aaa, ccc, "should be equal"); + } + + #[test] + fn transcript_test_01() { + let a = ScalarField::from(15); + let b = ScalarField::from(20); + + let [x, y, z] = Transcript::::from_scalar_number(&vec![a, b]).generate_challenges(); + let [x1, y1, z1] = Transcript::::from_scalar_number(&vec![a, b]).generate_challenges(); + + assert_eq!(x, x1, "should be equal"); + assert_eq!(y, y1, "should be equal"); + assert_eq!(z, z1, "should be equal"); + } + + #[test] + fn transcript_test_02() { + let a = ScalarField::from(15); + let b = ScalarField::from(20); + let commitment1 = KzgCommitment(G1Point::generator().mul(ScalarField::from(1)).into_affine()); + let commitment2 = KzgCommitment(G1Point::generator().mul(ScalarField::from(2)).into_affine()); + + let mut ts1 = Transcript::::default(); + let mut ts2 = Transcript::::default(); + + ts1.feed_scalar_num(a); + ts1.feed_scalar_num(b); + ts2.feed_scalar_num(a); + ts2.feed_scalar_num(b); + ts1.feed(&commitment1); + ts1.feed(&commitment2); + ts2.feed(&commitment1); + ts2.feed(&commitment2); + let [x, y, z] = ts1.generate_challenges(); + let [x1, y1, z1] = ts2.generate_challenges(); + assert_eq!(x, x1, "should be equal"); + assert_eq!(y, y1, "should be equal"); + assert_eq!(z, z1, "should be equal"); + + } + + #[test] + #[should_panic] + fn safe_guard() { + let commitment1 = KzgCommitment(G1Point::generator().mul(Fr::from(1)).into_affine()); + let commitments1: [KzgCommitment; 1] = [commitment1.clone()]; + let mut generator = Transcript::::from_commitment(&commitments1); + let [_a, _aa, _aaa] = generator.generate_challenges(); + let [_a, _aa, _aaa] = generator.generate_challenges(); + } +} diff --git a/nova/src/utils.rs b/nova/src/utils.rs new file mode 100644 index 0000000..027d1cc --- /dev/null +++ b/nova/src/utils.rs @@ -0,0 +1,64 @@ +use ark_ff::PrimeField; + +pub fn matrix_vector_product(matrix: &Vec>, z: &[F]) -> Vec { + let mut r: Vec = vec![F::zero(); matrix.len()]; + for i in 0..matrix.len() { + for j in 0..matrix[i].len() { + r[i] += matrix[i][j] * z[j]; + } + } + r +} +pub fn hadamard_product(a: &Vec, b: &Vec) -> Vec { + + let mut r: Vec = vec![F::zero(); a.len()]; + for i in 0..a.len() { + r[i] = a[i] * b[i]; + } + r +} + +pub fn vector_elem_product(a: &Vec, u: F) -> Vec { + let mut r: Vec = vec![F::zero(); a.len()]; + for i in 0..a.len() { + r[i] = a[i] * u; + } + r +} + +pub fn vec_sub(a: &Vec, b: &Vec) -> Vec { + assert_eq!(a.len(), b.len()); + let mut r: Vec = vec![F::zero(); a.len()]; + for i in 0..a.len() { + r[i] = a[i] - b[i]; + } + r +} + +pub fn vec_add(a: &Vec, b: &Vec) -> Vec { + assert_eq!(a.len(), b.len()); + let mut r: Vec = vec![F::zero(); a.len()]; + for i in 0..a.len() { + r[i] = a[i] + b[i]; + } + r +} + +pub fn to_f_matrix (matrix: Vec>) -> Vec> { + let mut r: Vec> = vec![Vec::new(); matrix.len()]; + for i in 0..matrix.len() { + r[i] = vec![F::zero(); matrix[i].len()]; + for j in 0..matrix[i].len() { + r[i][j] = F::from(matrix[i][j] as u64); + } + } + r +} + +pub fn to_f_vec(z: Vec) -> Vec { + let mut r: Vec = vec![F::zero(); z.len()]; + for i in 0..z.len() { + r[i] = F::from(z[i] as u64); + } + r +} \ No newline at end of file From cb46221b77b8f7bfea53e42218db3b0d8be8579f Mon Sep 17 00:00:00 2001 From: vanhger Date: Fri, 10 May 2024 08:51:36 +0700 Subject: [PATCH 04/18] Add Folding Scheme implementation. --- nova/src/lib.rs | 1 + nova/src/nifs/mod.rs | 3 + nova/src/nifs/nifs.rs | 188 +++++++++++++++++++++++++++++++++ nova/src/nifs/nifs_prover.rs | 39 +++++++ nova/src/nifs/nifs_verifier.rs | 143 +++++++++++++++++++++++++ 5 files changed, 374 insertions(+) create mode 100644 nova/src/nifs/mod.rs create mode 100644 nova/src/nifs/nifs.rs create mode 100644 nova/src/nifs/nifs_prover.rs create mode 100644 nova/src/nifs/nifs_verifier.rs diff --git a/nova/src/lib.rs b/nova/src/lib.rs index 64ddcae..c98baf4 100644 --- a/nova/src/lib.rs +++ b/nova/src/lib.rs @@ -1,2 +1,3 @@ +mod nifs; mod utils; mod transcript; \ No newline at end of file diff --git a/nova/src/nifs/mod.rs b/nova/src/nifs/mod.rs new file mode 100644 index 0000000..a53eaae --- /dev/null +++ b/nova/src/nifs/mod.rs @@ -0,0 +1,3 @@ +mod nifs_verifier; +mod nifs_prover; +mod nifs; diff --git a/nova/src/nifs/nifs.rs b/nova/src/nifs/nifs.rs new file mode 100644 index 0000000..a9b8748 --- /dev/null +++ b/nova/src/nifs/nifs.rs @@ -0,0 +1,188 @@ +use std::marker::PhantomData; +use std::ops::Mul; +use ark_ec::CurveGroup; +use ark_ff::fields::PrimeField; +use ark_ff::{One, Zero}; +use kzg::commitment::KzgCommitment; +use kzg::scheme::KzgScheme; +use kzg::types::ScalarField; +use sha2::{Digest}; +use crate::transcript::Transcript; +use crate::utils::{hadamard_product, matrix_vector_product, vec_add, vec_sub, vector_elem_product}; + +/// Create R1CS structure +#[derive(Clone)] +pub struct R1CS { + pub matrix_a: Vec>, + pub matrix_b: Vec>, + pub matrix_c: Vec>, +} + +/// Create Committed Relaxed R1CS Instance structure with KZG commitment +/// Todo: Need to impl a general-curve commitment. +#[derive(Debug)] +pub struct FInstance { + pub com_e: KzgCommitment, + pub u: ScalarField, + pub com_w: KzgCommitment, + pub x: Vec, +} + +/// Create Committed Relaxed FWitness with KZG commitment +/// Todo: Need to implement a general-curve commitment +pub struct FWitness { + pub e: Vec, + // pub rE: ScalarField, + pub w: Vec, + // pub rW: ScalarField, +} + +impl FWitness { + pub fn new(w: &Vec, len: usize) -> Self { + FWitness { + e: vec![ScalarField::zero(); len], + // rE: ScalarField::rand(&mut rand::thread_rng()), + w: w.clone(), + // rW: ScalarField::rand(&mut rand::thread_rng()), + } + } + + pub fn commit(&self, scheme: &KzgScheme, x: &Vec) -> FInstance { + let com_e = scheme.commit_vector(&self.e); + // cE.0 = cE.0.mul(self.rE).into_affine(); + let com_w = scheme.commit_vector(&self.w); + // cW.0 = cW.0.mul(self.rW).into_affine(); + + FInstance { + com_e: com_e, + u: ScalarField::one(), + com_w: com_w, + x: x.clone(), + } + } +} + +pub struct NIFS { + _phantom_data_t: PhantomData, +} + +impl NIFS { + + /// Compute the cross-term T + pub fn compute_t( + r1cs: &R1CS, + u1: ScalarField, + u2: ScalarField, + z1: &Vec, + z2: &Vec + ) -> Vec { + + let matrix_a = r1cs.clone().matrix_a; + let matrix_b = r1cs.clone().matrix_b; + let matrix_c = r1cs.clone().matrix_c; + + let az1 = matrix_vector_product(&matrix_a, z1); + let bz1 = matrix_vector_product(&matrix_b, z1); + let cz1 = matrix_vector_product(&matrix_c, z1); + let az2 = matrix_vector_product(&matrix_a, z2); + let bz2 = matrix_vector_product(&matrix_b, z2); + let cz2 = matrix_vector_product(&matrix_c, z2); + + let az1_bz2 = hadamard_product(&az1, &bz2); + let az2_bz1 = hadamard_product(&az2, &bz1); + let u1cz2 = vector_elem_product(&cz2, u1); + let u2cz1 = vector_elem_product(&cz1, u2); + + let mut t = vec_add(&az1_bz2, &az2_bz1); + t = vec_sub(&t, &u1cz2); + t = vec_sub(&t, &u2cz1); + + t + } + + pub fn fold_witness( + r: ScalarField, + fw1: &FWitness, + fw2: &FWitness, + t: &Vec, + // rT: ScalarField, + ) -> FWitness { + + let new_e = fw1.e.iter().zip(t.iter()).zip(&fw2.e).map(|((e1, t), e2)| { + *e1 + r * *t + r * r * *e2 + }).collect(); + + let new_w = fw1.w.iter().zip(&fw2.w).map(|(a, b)| { + *a + *b * r + }).collect(); + + FWitness{ + e: new_e, + w: new_w + } + } + + pub fn fold_instance( + r: ScalarField, + fi1: &FInstance, + fi2: &FInstance, + com_t: &KzgCommitment, + ) -> FInstance { + let new_com_e = KzgCommitment((fi1.com_e.0 + com_t.0.mul(r) + fi2.com_e.0.mul(r * r)).into_affine()); + let new_com_w = KzgCommitment((fi1.com_w.0 + fi2.com_w.0.mul(r)).into_affine()); + + let new_u = fi1.u + fi1.u * r; + let new_x = fi1.x.iter().zip(&fi1.x).map(|(a, b)| { + *a + *b * r + }).collect(); + + FInstance{ + com_e: new_com_e, + u: new_u, + com_w: new_com_w, + x: new_x + } + } + + pub fn prover( + r1cs: &R1CS, + fw1: &FWitness, + fw2: &FWitness, + fi1: &FInstance, + fi2: &FInstance, + scheme: &KzgScheme, + transcript: &mut Transcript + ) -> (FWitness, FInstance, KzgCommitment, ScalarField) { + let mut z1 = fw1.w.clone(); + z1.append(&mut fi1.x.clone()); + z1.push(fi1.u); + + let mut z2 = fw2.w.clone(); + z2.append(&mut fi2.x.clone()); + z2.push(fi2.u); + + let t = NIFS::::compute_t(&r1cs, fi1.u, fi2.u, &z1, &z2); + let com_t = scheme.commit_vector(&t); + + transcript.feed_scalar_num(fi1.u); + transcript.feed_scalar_num(fi2.u); + transcript.feed(&com_t); + let [r] = transcript.generate_challenges(); + + let new_witness = NIFS::::fold_witness(r, fw1, fw2, &t); + let new_instance = NIFS::::fold_instance(r, fi1, fi2, &com_t); + + (new_witness, new_instance, com_t, r) + } + + pub fn verifier( + r: ScalarField, + fi1: &FInstance, + fi2: &FInstance, + com_t: &KzgCommitment, + ) -> FInstance { + NIFS::::fold_instance(r, fi1, fi2, com_t) + } + +} + diff --git a/nova/src/nifs/nifs_prover.rs b/nova/src/nifs/nifs_prover.rs new file mode 100644 index 0000000..fe514ff --- /dev/null +++ b/nova/src/nifs/nifs_prover.rs @@ -0,0 +1,39 @@ +use sha2::Digest; +use kzg::opening::KzgOpening; +use kzg::scheme::KzgScheme; +use kzg::types::ScalarField; +use crate::nifs::nifs::{FInstance, FWitness, NIFS}; +use crate::transcript::Transcript; + +pub struct NIFSProof { + pub r: ScalarField, + pub opening_point: ScalarField, + pub opening_e: KzgOpening, + pub opening_w: KzgOpening +} + +impl NIFS { + pub fn prove( + r: ScalarField, + fw: &FWitness, + fi: &FInstance, + scheme: &KzgScheme, + transcript: &mut Transcript, + ) -> NIFSProof { + + // opening = Transcript(fi_cmE, fi_cmW); + transcript.feed(&fi.com_e); + transcript.feed(&fi.com_w); + let [opening_point] = transcript.generate_challenges(); + + let opening_e = scheme.open_vector(&fw.e, opening_point); + let opening_w = scheme.open_vector(&fw.w, opening_point); + + NIFSProof { + r, + opening_point, + opening_e: opening_e, + opening_w: opening_w + } + } +} diff --git a/nova/src/nifs/nifs_verifier.rs b/nova/src/nifs/nifs_verifier.rs new file mode 100644 index 0000000..96ca49d --- /dev/null +++ b/nova/src/nifs/nifs_verifier.rs @@ -0,0 +1,143 @@ +use ark_ff::PrimeField; +use sha2::Digest; +use kzg::commitment::KzgCommitment; +use kzg::scheme::KzgScheme; +use crate::nifs::nifs::{FInstance, NIFS, R1CS}; +use crate::nifs::nifs_prover::NIFSProof; +use crate::transcript::Transcript; +use crate::utils::{to_f_matrix, to_f_vec}; + + +impl NIFS { + pub fn verify( + proof: NIFSProof, + fi1: &FInstance, + fi2: &FInstance, + fi3: &FInstance, // folded instance. + com_t: &KzgCommitment, + scheme: &KzgScheme, + transcript: &mut Transcript + ) -> Result<(), String> { + + transcript.feed_scalar_num(fi1.u); + transcript.feed_scalar_num(fi2.u); + transcript.feed(&com_t); + // Verify that proof.r = Transcript(fi1.u, fi2.u, cmT) + let [r] = transcript.generate_challenges(); + // println!("verifier_r: {:?}", r); + // println!("prover_r: {:?}", proof.r); + // println!("{:?}", r == proof.r); + if r != proof.r { + return Err(String::from("Verify: Error in computing random r")) + } + + transcript.feed(&fi3.com_e); + transcript.feed(&fi3.com_w); + // Verify Opening_point = Transcript(fi1.cmE, fi1.cmW) + let [opening_point] = transcript.generate_challenges(); + if opening_point != proof.opening_point { + return Err(String::from("Verify: Error in computing random opening point")) + } + // println!("fi.cW is2: {:?}", fi3.cW); + // println!("openingW is2: {:?}", proof.openingW); + // println!("opening_point is2: {:?}", opening_point); + + if !scheme.verify(&fi3.com_w, &proof.opening_w, opening_point) { + return Err(String::from("Verify: Folding wrong at W")); + } + + if !scheme.verify(&fi3.com_e, &proof.opening_e, opening_point) { + return Err(String::from("Verify: Folding wrong at E")); + } + + Ok(()) + } +} + +pub fn gen_test_values(n: usize) -> (R1CS, Vec>, Vec>) { + // R1CS for: x^3 + x + 5 = y (example from article + // https://vitalik.eth.limo/general/2016/12/10/qap.html ) + + let a = to_f_matrix::(vec![ + vec![0, 1, 0, 0, 0, 0], + vec![0, 0, 0, 1, 0, 0], + vec![0, 1, 0, 0, 1, 0], + vec![5, 0, 0, 0, 0, 1], + ]); + let b = to_f_matrix::(vec![ + vec![0, 1, 0, 0, 0, 0], + vec![0, 1, 0, 0, 0, 0], + vec![1, 0, 0, 0, 0, 0], + vec![1, 0, 0, 0, 0, 0], + ]); + let c = to_f_matrix::(vec![ + vec![0, 0, 0, 1, 0, 0], + vec![0, 0, 0, 0, 1, 0], + vec![0, 0, 0, 0, 0, 1], + vec![0, 0, 1, 0, 0, 0], + ]); + + // generate n witnesses + let mut w: Vec> = Vec::new(); + let mut x: Vec> = Vec::new(); + for i in 0..n { + let input = 3 + i; + let w_i = to_f_vec::(vec![ + 1, + input, + input * input * input + input + 5, // x^3 + x + 5 + input * input, // x^2 + input * input * input, // x^2 * x + input * input * input + input, // x^3 + x + ]); + w.push(w_i.clone()); + let x_i = to_f_vec::(vec![input * input * input + input + 5]); + x.push(x_i.clone()); + } + // println!("w: {:?}", w); + // println!("x: {:?}", x); + + let r1cs = R1CS:: { matrix_a: a, matrix_b: b, matrix_c: c }; + (r1cs, w, x) +} + +#[cfg(test)] + +mod tests { + use super::*; + use sha2::Sha256; + use kzg::srs::Srs; + use nifs::FWitness; + use crate::nifs::nifs; + use crate::nifs::nifs::NIFS; + + #[test] + fn test_one_fold() { + /// generate R1CS, witnesses and public input, output. + let (r1cs, witnesses, x) = gen_test_values(2); + let (matrix_a, matrix_b, matrix_c) = (r1cs.matrix_a.clone(), r1cs.matrix_b.clone(), r1cs.matrix_c.clone()); + + /// Trusted setup + let domain_size = witnesses[0].len() + x[0].len() + 1; + let srs = Srs::new(domain_size); + let scheme = KzgScheme::new(srs); + + let mut prover_transcript = Transcript::::default(); + let mut verifier_transcript = Transcript::::default(); + + let fw1 = FWitness::new(&witnesses[0], matrix_a.len()); + let fw2 = FWitness::new(&witnesses[1], matrix_a.len()); + + let fi1 = fw1.commit(&scheme, &x[0]); + let fi2 = fw2.commit(&scheme, &x[1]); + + let (p_folded_witness, p_folded_instance, com_t, r) = NIFS::prover(&r1cs, &fw1, &fw2, &fi1, &fi2, &scheme, &mut prover_transcript); + + let proof = NIFS::::prove(r, &p_folded_witness, &p_folded_instance, &scheme, &mut prover_transcript); + let v_folded_instance = NIFS::::verifier(r, &fi1, &fi2, &com_t); + + let result = NIFS::::verify(proof, &fi1, &fi2, &v_folded_instance, &com_t, &scheme, &mut verifier_transcript); + println!("{:?}", result); + assert!(result.is_ok()); + } +} \ No newline at end of file From 6bdef5686cd002592206419209ac2437025c17fc Mon Sep 17 00:00:00 2001 From: vanhger Date: Fri, 10 May 2024 22:33:07 +0700 Subject: [PATCH 05/18] Add satisfaction condition for R1CS --- kzg/src/types.rs | 1 + nova/src/lib.rs | 4 +- nova/src/nifs/mod.rs | 143 ++++++++++++++++++++++++- nova/src/nifs/nifs.rs | 188 --------------------------------- nova/src/nifs/nifs_prover.rs | 8 +- nova/src/nifs/nifs_verifier.rs | 15 ++- nova/src/r1cs/mod.rs | 106 +++++++++++++++++++ nova/src/utils.rs | 15 ++- 8 files changed, 273 insertions(+), 207 deletions(-) delete mode 100644 nova/src/nifs/nifs.rs create mode 100644 nova/src/r1cs/mod.rs diff --git a/kzg/src/types.rs b/kzg/src/types.rs index 3f98058..ba7ea0f 100644 --- a/kzg/src/types.rs +++ b/kzg/src/types.rs @@ -6,4 +6,5 @@ use ark_poly::univariate::DensePolynomial; pub type G1Point = as Pairing>::G1Affine; pub type G2Point = as Pairing>::G2Affine; pub type ScalarField = as Pairing>::ScalarField; +pub type BaseField = as Pairing>::BaseField; pub type Poly = DensePolynomial; diff --git a/nova/src/lib.rs b/nova/src/lib.rs index c98baf4..987834f 100644 --- a/nova/src/lib.rs +++ b/nova/src/lib.rs @@ -1,3 +1,5 @@ mod nifs; mod utils; -mod transcript; \ No newline at end of file +mod transcript; +mod circuit; +mod r1cs; \ No newline at end of file diff --git a/nova/src/nifs/mod.rs b/nova/src/nifs/mod.rs index a53eaae..08bc39f 100644 --- a/nova/src/nifs/mod.rs +++ b/nova/src/nifs/mod.rs @@ -1,3 +1,144 @@ +use std::marker::PhantomData; +use std::ops::Mul; +use ark_ec::CurveGroup; +use ark_ff::fields::PrimeField; + +use kzg::commitment::KzgCommitment; +use kzg::scheme::KzgScheme; +use kzg::types::ScalarField; +use sha2::{Digest}; +use kzg::opening::KzgOpening; +use crate::r1cs::{FInstance, FWitness, R1CS}; +use crate::transcript::Transcript; +use crate::utils::{hadamard_product, matrix_vector_product, vec_add, vec_sub, vector_elem_product}; + mod nifs_verifier; mod nifs_prover; -mod nifs; + +pub struct NIFSProof { + pub r: ScalarField, + pub opening_point: ScalarField, + pub opening_e: KzgOpening, + pub opening_w: KzgOpening +} + +pub struct NIFS { + _phantom_data_t: PhantomData, +} + +impl NIFS { + + /// Compute the cross-term T + pub fn compute_t( + r1cs: &R1CS, + u1: ScalarField, + u2: ScalarField, + z1: &Vec, + z2: &Vec + ) -> Vec { + + let az1 = matrix_vector_product(&r1cs.matrix_a, z1); + let bz1 = matrix_vector_product(&r1cs.matrix_b, z1); + let cz1 = matrix_vector_product(&r1cs.matrix_c, z1); + let az2 = matrix_vector_product(&r1cs.matrix_a, z2); + let bz2 = matrix_vector_product(&r1cs.matrix_b, z2); + let cz2 = matrix_vector_product(&r1cs.matrix_c, z2); + + let az1_bz2 = hadamard_product(&az1, &bz2); + let az2_bz1 = hadamard_product(&az2, &bz1); + let u1cz2 = vector_elem_product(&cz2, u1); + let u2cz1 = vector_elem_product(&cz1, u2); + + let mut t = vec_add(&az1_bz2, &az2_bz1); + t = vec_sub(&t, &u1cz2); + t = vec_sub(&t, &u2cz1); + + t + } + + pub fn fold_witness( + r: ScalarField, + fw1: &FWitness, + fw2: &FWitness, + t: &Vec, + // rT: ScalarField, + ) -> FWitness { + + let new_e = fw1.e.iter().zip(t.iter()).zip(&fw2.e).map(|((e1, t), e2)| { + *e1 + r * *t + r * r * *e2 + }).collect(); + + let new_w = fw1.w.iter().zip(&fw2.w).map(|(a, b)| { + *a + *b * r + }).collect(); + + FWitness{ + e: new_e, + w: new_w + } + } + + pub fn fold_instance( + r: ScalarField, + fi1: &FInstance, + fi2: &FInstance, + com_t: &KzgCommitment, + ) -> FInstance { + let new_com_e = KzgCommitment((fi1.com_e.0 + com_t.0.mul(r) + fi2.com_e.0.mul(r * r)).into_affine()); + let new_com_w = KzgCommitment((fi1.com_w.0 + fi2.com_w.0.mul(r)).into_affine()); + + let new_u = fi1.u + fi1.u * r; + let new_x = fi1.x.iter().zip(&fi1.x).map(|(a, b)| { + *a + *b * r + }).collect(); + + FInstance{ + com_e: new_com_e, + u: new_u, + com_w: new_com_w, + x: new_x + } + } + + pub fn prover( + r1cs: &R1CS, + fw1: &FWitness, + fw2: &FWitness, + fi1: &FInstance, + fi2: &FInstance, + scheme: &KzgScheme, + transcript: &mut Transcript + ) -> (FWitness, FInstance, KzgCommitment, ScalarField) { + let mut z1 = fw1.w.clone(); + z1.append(&mut fi1.x.clone()); + z1.push(fi1.u); + + let mut z2 = fw2.w.clone(); + z2.append(&mut fi2.x.clone()); + z2.push(fi2.u); + + let t = NIFS::::compute_t(&r1cs, fi1.u, fi2.u, &z1, &z2); + let com_t = scheme.commit_vector(&t); + + transcript.feed_scalar_num(fi1.u); + transcript.feed_scalar_num(fi2.u); + transcript.feed(&com_t); + let [r] = transcript.generate_challenges(); + + let new_witness = NIFS::::fold_witness(r, fw1, fw2, &t); + let new_instance = NIFS::::fold_instance(r, fi1, fi2, &com_t); + + (new_witness, new_instance, com_t, r) + } + + pub fn verifier( + r: ScalarField, + fi1: &FInstance, + fi2: &FInstance, + com_t: &KzgCommitment, + ) -> FInstance { + NIFS::::fold_instance(r, fi1, fi2, com_t) + } + +} + diff --git a/nova/src/nifs/nifs.rs b/nova/src/nifs/nifs.rs deleted file mode 100644 index a9b8748..0000000 --- a/nova/src/nifs/nifs.rs +++ /dev/null @@ -1,188 +0,0 @@ -use std::marker::PhantomData; -use std::ops::Mul; -use ark_ec::CurveGroup; -use ark_ff::fields::PrimeField; -use ark_ff::{One, Zero}; -use kzg::commitment::KzgCommitment; -use kzg::scheme::KzgScheme; -use kzg::types::ScalarField; -use sha2::{Digest}; -use crate::transcript::Transcript; -use crate::utils::{hadamard_product, matrix_vector_product, vec_add, vec_sub, vector_elem_product}; - -/// Create R1CS structure -#[derive(Clone)] -pub struct R1CS { - pub matrix_a: Vec>, - pub matrix_b: Vec>, - pub matrix_c: Vec>, -} - -/// Create Committed Relaxed R1CS Instance structure with KZG commitment -/// Todo: Need to impl a general-curve commitment. -#[derive(Debug)] -pub struct FInstance { - pub com_e: KzgCommitment, - pub u: ScalarField, - pub com_w: KzgCommitment, - pub x: Vec, -} - -/// Create Committed Relaxed FWitness with KZG commitment -/// Todo: Need to implement a general-curve commitment -pub struct FWitness { - pub e: Vec, - // pub rE: ScalarField, - pub w: Vec, - // pub rW: ScalarField, -} - -impl FWitness { - pub fn new(w: &Vec, len: usize) -> Self { - FWitness { - e: vec![ScalarField::zero(); len], - // rE: ScalarField::rand(&mut rand::thread_rng()), - w: w.clone(), - // rW: ScalarField::rand(&mut rand::thread_rng()), - } - } - - pub fn commit(&self, scheme: &KzgScheme, x: &Vec) -> FInstance { - let com_e = scheme.commit_vector(&self.e); - // cE.0 = cE.0.mul(self.rE).into_affine(); - let com_w = scheme.commit_vector(&self.w); - // cW.0 = cW.0.mul(self.rW).into_affine(); - - FInstance { - com_e: com_e, - u: ScalarField::one(), - com_w: com_w, - x: x.clone(), - } - } -} - -pub struct NIFS { - _phantom_data_t: PhantomData, -} - -impl NIFS { - - /// Compute the cross-term T - pub fn compute_t( - r1cs: &R1CS, - u1: ScalarField, - u2: ScalarField, - z1: &Vec, - z2: &Vec - ) -> Vec { - - let matrix_a = r1cs.clone().matrix_a; - let matrix_b = r1cs.clone().matrix_b; - let matrix_c = r1cs.clone().matrix_c; - - let az1 = matrix_vector_product(&matrix_a, z1); - let bz1 = matrix_vector_product(&matrix_b, z1); - let cz1 = matrix_vector_product(&matrix_c, z1); - let az2 = matrix_vector_product(&matrix_a, z2); - let bz2 = matrix_vector_product(&matrix_b, z2); - let cz2 = matrix_vector_product(&matrix_c, z2); - - let az1_bz2 = hadamard_product(&az1, &bz2); - let az2_bz1 = hadamard_product(&az2, &bz1); - let u1cz2 = vector_elem_product(&cz2, u1); - let u2cz1 = vector_elem_product(&cz1, u2); - - let mut t = vec_add(&az1_bz2, &az2_bz1); - t = vec_sub(&t, &u1cz2); - t = vec_sub(&t, &u2cz1); - - t - } - - pub fn fold_witness( - r: ScalarField, - fw1: &FWitness, - fw2: &FWitness, - t: &Vec, - // rT: ScalarField, - ) -> FWitness { - - let new_e = fw1.e.iter().zip(t.iter()).zip(&fw2.e).map(|((e1, t), e2)| { - *e1 + r * *t + r * r * *e2 - }).collect(); - - let new_w = fw1.w.iter().zip(&fw2.w).map(|(a, b)| { - *a + *b * r - }).collect(); - - FWitness{ - e: new_e, - w: new_w - } - } - - pub fn fold_instance( - r: ScalarField, - fi1: &FInstance, - fi2: &FInstance, - com_t: &KzgCommitment, - ) -> FInstance { - let new_com_e = KzgCommitment((fi1.com_e.0 + com_t.0.mul(r) + fi2.com_e.0.mul(r * r)).into_affine()); - let new_com_w = KzgCommitment((fi1.com_w.0 + fi2.com_w.0.mul(r)).into_affine()); - - let new_u = fi1.u + fi1.u * r; - let new_x = fi1.x.iter().zip(&fi1.x).map(|(a, b)| { - *a + *b * r - }).collect(); - - FInstance{ - com_e: new_com_e, - u: new_u, - com_w: new_com_w, - x: new_x - } - } - - pub fn prover( - r1cs: &R1CS, - fw1: &FWitness, - fw2: &FWitness, - fi1: &FInstance, - fi2: &FInstance, - scheme: &KzgScheme, - transcript: &mut Transcript - ) -> (FWitness, FInstance, KzgCommitment, ScalarField) { - let mut z1 = fw1.w.clone(); - z1.append(&mut fi1.x.clone()); - z1.push(fi1.u); - - let mut z2 = fw2.w.clone(); - z2.append(&mut fi2.x.clone()); - z2.push(fi2.u); - - let t = NIFS::::compute_t(&r1cs, fi1.u, fi2.u, &z1, &z2); - let com_t = scheme.commit_vector(&t); - - transcript.feed_scalar_num(fi1.u); - transcript.feed_scalar_num(fi2.u); - transcript.feed(&com_t); - let [r] = transcript.generate_challenges(); - - let new_witness = NIFS::::fold_witness(r, fw1, fw2, &t); - let new_instance = NIFS::::fold_instance(r, fi1, fi2, &com_t); - - (new_witness, new_instance, com_t, r) - } - - pub fn verifier( - r: ScalarField, - fi1: &FInstance, - fi2: &FInstance, - com_t: &KzgCommitment, - ) -> FInstance { - NIFS::::fold_instance(r, fi1, fi2, com_t) - } - -} - diff --git a/nova/src/nifs/nifs_prover.rs b/nova/src/nifs/nifs_prover.rs index fe514ff..2a9b924 100644 --- a/nova/src/nifs/nifs_prover.rs +++ b/nova/src/nifs/nifs_prover.rs @@ -2,15 +2,9 @@ use sha2::Digest; use kzg::opening::KzgOpening; use kzg::scheme::KzgScheme; use kzg::types::ScalarField; -use crate::nifs::nifs::{FInstance, FWitness, NIFS}; +use crate::nifs::{FInstance, FWitness, NIFS, NIFSProof}; use crate::transcript::Transcript; -pub struct NIFSProof { - pub r: ScalarField, - pub opening_point: ScalarField, - pub opening_e: KzgOpening, - pub opening_w: KzgOpening -} impl NIFS { pub fn prove( diff --git a/nova/src/nifs/nifs_verifier.rs b/nova/src/nifs/nifs_verifier.rs index 96ca49d..483fc2d 100644 --- a/nova/src/nifs/nifs_verifier.rs +++ b/nova/src/nifs/nifs_verifier.rs @@ -2,8 +2,7 @@ use ark_ff::PrimeField; use sha2::Digest; use kzg::commitment::KzgCommitment; use kzg::scheme::KzgScheme; -use crate::nifs::nifs::{FInstance, NIFS, R1CS}; -use crate::nifs::nifs_prover::NIFSProof; +use crate::nifs::{FInstance, NIFS, R1CS, NIFSProof}; use crate::transcript::Transcript; use crate::utils::{to_f_matrix, to_f_vec}; @@ -97,7 +96,7 @@ pub fn gen_test_values(n: usize) -> (R1CS, Vec>, Vec { matrix_a: a, matrix_b: b, matrix_c: c }; + let r1cs = R1CS:: { matrix_a: a, matrix_b: b, matrix_c: c, num_io: 1, num_vars: 6 }; (r1cs, w, x) } @@ -107,17 +106,15 @@ mod tests { use super::*; use sha2::Sha256; use kzg::srs::Srs; - use nifs::FWitness; - use crate::nifs::nifs; - use crate::nifs::nifs::NIFS; + use crate::nifs::{FWitness, NIFS}; #[test] fn test_one_fold() { - /// generate R1CS, witnesses and public input, output. + // generate R1CS, witnesses and public input, output. let (r1cs, witnesses, x) = gen_test_values(2); - let (matrix_a, matrix_b, matrix_c) = (r1cs.matrix_a.clone(), r1cs.matrix_b.clone(), r1cs.matrix_c.clone()); + let (matrix_a, _, _) = (r1cs.matrix_a.clone(), r1cs.matrix_b.clone(), r1cs.matrix_c.clone()); - /// Trusted setup + // Trusted setup let domain_size = witnesses[0].len() + x[0].len() + 1; let srs = Srs::new(domain_size); let scheme = KzgScheme::new(srs); diff --git a/nova/src/r1cs/mod.rs b/nova/src/r1cs/mod.rs new file mode 100644 index 0000000..04067cd --- /dev/null +++ b/nova/src/r1cs/mod.rs @@ -0,0 +1,106 @@ +use ark_ff::{PrimeField, Zero, One}; +use kzg::commitment::KzgCommitment; +use kzg::scheme::KzgScheme; +use kzg::types::ScalarField; +use crate::utils::{hadamard_product, matrix_vector_product, vec_add, vec_equal, vector_elem_product}; + +/// Create R1CS structure +#[derive(Clone)] +pub struct R1CS { + pub matrix_a: Vec>, + pub matrix_b: Vec>, + pub matrix_c: Vec>, + pub num_io: usize, + pub num_vars: usize, +} + +/// Create Committed Relaxed R1CS Instance structure with KZG commitment +/// Todo: Need to impl a general-curve commitment. +#[derive(Debug)] +pub struct FInstance { + pub com_e: KzgCommitment, + pub u: ScalarField, + pub com_w: KzgCommitment, + pub x: Vec, +} + + +/// Create Committed Relaxed FWitness with KZG commitment +/// Todo: Need to implement a general-curve commitment +pub struct FWitness { + pub e: Vec, + // pub rE: ScalarField, + pub w: Vec, + // pub rW: ScalarField, +} + +impl FWitness { + pub fn new(w: &Vec, len: usize) -> Self { + FWitness { + e: vec![ScalarField::zero(); len], + // rE: ScalarField::rand(&mut rand::thread_rng()), + w: w.clone(), + // rW: ScalarField::rand(&mut rand::thread_rng()), + } + } + + pub fn create_trivial_witness(len: usize) -> Self { + FWitness { + e: vec![ScalarField::zero(); len], + w: vec![ScalarField::zero(); len], + } + } + + pub fn commit(&self, scheme: &KzgScheme, x: &Vec) -> FInstance { + let com_e = scheme.commit_vector(&self.e); + // cE.0 = cE.0.mul(self.rE).into_affine(); + let com_w = scheme.commit_vector(&self.w); + // cW.0 = cW.0.mul(self.rW).into_affine(); + + FInstance { + com_e: com_e, + u: ScalarField::one(), + com_w: com_w, + x: x.clone(), + } + } +} + +pub fn is_satis_relaxed( + r1cs: R1CS, + f_instance: FInstance, + f_witness: FWitness, + scheme: KzgScheme +) -> Result<(), String> { + if r1cs.num_vars != f_witness.w.len() { + return Err(String::from("Witness does not match with matrices")); + } + if r1cs.num_io != f_instance.x.len() { + return Err(String::from("Instance does not match with matrices")); + } + + // check if: Az * Bz = u*Cz + E + let mut z = f_witness.w.clone(); + z.append(&mut f_instance.x.clone()); + z.push(f_instance.u); + + let az = matrix_vector_product(&r1cs.matrix_a, &z); + let bz = matrix_vector_product(&r1cs.matrix_b, &z); + let cz = matrix_vector_product(&r1cs.matrix_c, &z); + + let left_side = hadamard_product(&az, &bz); + let ucz = vector_elem_product(&cz, f_instance.u); + let right_side = vec_add(&ucz, &f_witness.e); + + let res_eq = vec_equal(&left_side, &right_side); + + // check whether Instance satisfies Witness + let res_com = (f_instance.com_w == scheme.commit_vector(&f_witness.w)) && (f_instance.com_e == scheme.commit_vector(&f_witness.e)); + + if res_com && res_eq { + Ok(()) + } else { + return Err(String::from("Instance does not satisfy the Witness.")) + } + +} diff --git a/nova/src/utils.rs b/nova/src/utils.rs index 027d1cc..df15373 100644 --- a/nova/src/utils.rs +++ b/nova/src/utils.rs @@ -1,6 +1,6 @@ use ark_ff::PrimeField; -pub fn matrix_vector_product(matrix: &Vec>, z: &[F]) -> Vec { +pub fn matrix_vector_product(matrix: &Vec>, z: &Vec) -> Vec { let mut r: Vec = vec![F::zero(); matrix.len()]; for i in 0..matrix.len() { for j in 0..matrix[i].len() { @@ -44,6 +44,19 @@ pub fn vec_add(a: &Vec, b: &Vec) -> Vec { r } +pub fn vec_equal(a: &Vec, b: &Vec) -> bool { + if a.len() != b.len() { + return false; + } + + for i in 0..a.len() { + if a[i] != b[i] { + return false; + } + } + true +} + pub fn to_f_matrix (matrix: Vec>) -> Vec> { let mut r: Vec> = vec![Vec::new(); matrix.len()]; for i in 0..matrix.len() { From b75cb926ca22f23b365893e25104fcdb1d977c76 Mon Sep 17 00:00:00 2001 From: vanhger Date: Sun, 12 May 2024 14:02:08 +0700 Subject: [PATCH 06/18] Separate the NIFS verification process into 2 different functions --- nova/src/nifs/mod.rs | 1 - nova/src/nifs/nifs_verifier.rs | 56 ++++++++++++++++++++++++++-------- 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/nova/src/nifs/mod.rs b/nova/src/nifs/mod.rs index 08bc39f..23b7702 100644 --- a/nova/src/nifs/mod.rs +++ b/nova/src/nifs/mod.rs @@ -1,7 +1,6 @@ use std::marker::PhantomData; use std::ops::Mul; use ark_ec::CurveGroup; -use ark_ff::fields::PrimeField; use kzg::commitment::KzgCommitment; use kzg::scheme::KzgScheme; diff --git a/nova/src/nifs/nifs_verifier.rs b/nova/src/nifs/nifs_verifier.rs index 483fc2d..27ac3a1 100644 --- a/nova/src/nifs/nifs_verifier.rs +++ b/nova/src/nifs/nifs_verifier.rs @@ -2,6 +2,8 @@ use ark_ff::PrimeField; use sha2::Digest; use kzg::commitment::KzgCommitment; use kzg::scheme::KzgScheme; +use kzg::types::ScalarField; +use plonk::prover::Proof; use crate::nifs::{FInstance, NIFS, R1CS, NIFSProof}; use crate::transcript::Transcript; use crate::utils::{to_f_matrix, to_f_vec}; @@ -9,7 +11,7 @@ use crate::utils::{to_f_matrix, to_f_vec}; impl NIFS { pub fn verify( - proof: NIFSProof, + proof: &NIFSProof, fi1: &FInstance, fi2: &FInstance, fi3: &FInstance, // folded instance. @@ -18,29 +20,57 @@ impl NIFS { transcript: &mut Transcript ) -> Result<(), String> { - transcript.feed_scalar_num(fi1.u); - transcript.feed_scalar_num(fi2.u); + // verify challenge. + let mut res = Self::verify_challenge(proof.r, fi1.u, fi2.u, com_t, transcript); + if res.is_err() { + return res; + } + + // verify opening. + res = Self::verify_opening(proof, fi3, scheme, transcript); + if res.is_err() { + return res; + } + + Ok(()) + } + + pub fn verify_challenge( + r: ScalarField, + fi1_u: ScalarField, + fi2_u: ScalarField, + com_t: &KzgCommitment, + transcript: &mut Transcript + ) -> Result<(), String> { + // recreate folded instance + transcript.feed_scalar_num(fi1_u); + transcript.feed_scalar_num(fi2_u); transcript.feed(&com_t); // Verify that proof.r = Transcript(fi1.u, fi2.u, cmT) - let [r] = transcript.generate_challenges(); - // println!("verifier_r: {:?}", r); - // println!("prover_r: {:?}", proof.r); - // println!("{:?}", r == proof.r); - if r != proof.r { + let [new_r] = transcript.generate_challenges(); + + // verify challenge r + if new_r != r { return Err(String::from("Verify: Error in computing random r")) } + Ok(()) + } + pub fn verify_opening( + proof: &NIFSProof, + fi3: &FInstance, // folded instance. + scheme: &KzgScheme, + transcript: &mut Transcript + ) -> Result<(), String> { transcript.feed(&fi3.com_e); transcript.feed(&fi3.com_w); // Verify Opening_point = Transcript(fi1.cmE, fi1.cmW) let [opening_point] = transcript.generate_challenges(); if opening_point != proof.opening_point { - return Err(String::from("Verify: Error in computing random opening point")) + return Err(String::from("Verify: Error in computing random opening point")); } - // println!("fi.cW is2: {:?}", fi3.cW); - // println!("openingW is2: {:?}", proof.openingW); - // println!("opening_point is2: {:?}", opening_point); + // Verify opening if !scheme.verify(&fi3.com_w, &proof.opening_w, opening_point) { return Err(String::from("Verify: Folding wrong at W")); } @@ -133,7 +163,7 @@ mod tests { let proof = NIFS::::prove(r, &p_folded_witness, &p_folded_instance, &scheme, &mut prover_transcript); let v_folded_instance = NIFS::::verifier(r, &fi1, &fi2, &com_t); - let result = NIFS::::verify(proof, &fi1, &fi2, &v_folded_instance, &com_t, &scheme, &mut verifier_transcript); + let result = NIFS::::verify(&proof, &fi1, &fi2, &v_folded_instance, &com_t, &scheme, &mut verifier_transcript); println!("{:?}", result); assert!(result.is_ok()); } From 77f6f53c264e10f328d8992f0755e1aff9b47d84 Mon Sep 17 00:00:00 2001 From: vanhger Date: Sun, 12 May 2024 17:03:32 +0700 Subject: [PATCH 07/18] Write a simple version of AugmentedCircuit --- nova/src/circuit.rs | 140 ++++++++++++++++++++++++++++++++++++++++++++ nova/src/ivc/mod.rs | 15 +++++ nova/src/lib.rs | 1 + 3 files changed, 156 insertions(+) create mode 100644 nova/src/circuit.rs create mode 100644 nova/src/ivc/mod.rs diff --git a/nova/src/circuit.rs b/nova/src/circuit.rs new file mode 100644 index 0000000..e426ad7 --- /dev/null +++ b/nova/src/circuit.rs @@ -0,0 +1,140 @@ +use std::marker::PhantomData; +use std::ops::Add; +use ark_ec::CurveGroup; +use ark_ff::{BigInteger, Field, One, PrimeField}; +use ark_serialize::CanonicalSerialize; +use ark_std::UniformRand; +use rand::prelude::StdRng; +use rand::SeedableRng; +use sha2::Digest; +use kzg::commitment::KzgCommitment; +use kzg::types::{BaseField, ScalarField}; +use crate::nifs::{NIFS}; +use crate::r1cs::{FInstance, FWitness}; +use crate::transcript::Transcript; + +pub type ConstraintF = <::BaseField as Field>::BasePrimeField; + +/// State structure of IVC, which is presented in BaseField of Bsn12_381 curve +/// Todo: Implement a general version. +#[derive(Clone)] +pub struct State { + pub state: BaseField, +} +pub struct FCircuit { + // state u_i + u_i: State, + // state u_{i+1} + u_i1: State, +} + +impl FCircuit { + pub fn run(&self, fwitness: &FWitness) { + + } +} + +/// F' circuit +pub struct AugmentedCircuit { + pub f_circuit: FCircuit, + pub i: BaseField, + pub trivial_instance: FInstance, // trivial instance. + pub z_0: State, + pub z_i: State, + + phantom_data: PhantomData, +} + +impl AugmentedCircuit { + pub fn run( + &self, + u_i: &FInstance, + w_i: &FWitness, + big_u_i: &FInstance, + r: Option, + com_t: Option<&KzgCommitment>, + transcript: &mut Transcript, + ) -> Result<(State, ScalarField), String>{ + // compute hash(i, z_0, z_i, U_i) + let hash_x = Self::hash_io(self.i, &self.z_0, &self.z_i, big_u_i); + + if self.i != BaseField::from(0) { + + // 1. check that u.x =? hash + // Because u_i.x is in ScalarField while hash is in BaseField, they need to + // be converted into a comparable type + // Todo: Non-native field transform + let u_dot_x = u_i.x[0].clone(); + let hash_fr = ScalarField::from_le_bytes_mod_order(&hash_x.into_bigint().to_bytes_le()); + if u_dot_x != hash_fr { + return Err(String::from("Public IO is wrong ")); + } + + // 2. check that u_i.comE = com_([0,...]) and u_i.u = 1 + if u_i.com_e != self.trivial_instance.com_e { + return Err(String::from("Commitment of E is wrong")); + } + if u_i.u.is_one() { + return Err(String::from("Commitment of E is wrong")); + } + + // 3. verify challenge r + let challenge_checker = NIFS::::verify_challenge(r.unwrap(), u_i.u, big_u_i.u, com_t.unwrap(), transcript); + if challenge_checker.is_err() { + return Err(challenge_checker.unwrap_err()); + } + + // 3.compute U_{i+1} + let big_u_i1 = NIFS::::verifier(r.unwrap(), u_i, big_u_i, com_t.unwrap()); + + // compute z_{i+1} = F(z_i, w_i) + self.f_circuit.run(w_i); + let z_i1 = self.f_circuit.u_i1.clone(); + + // compute hash + let mut new_hash= Self::hash_io(self.i.add(BaseField::one()), &self.z_0, &z_i1, &big_u_i1); + // convert into ScalarField + let new_x = ScalarField::from_le_bytes_mod_order(&new_hash.into_bigint().to_bytes_le()); + return Ok((z_i1, new_x)); + } else { // i == 0 + + // compute z_1 = F(z_0, w_i) + self.f_circuit.run(w_i); + let z_i1 = self.f_circuit.u_i1.clone(); + let new_hash = Self::hash_io(BaseField::one(), &self.z_0, &z_i1, &self.trivial_instance); + // convert into ScalarField + let new_x = ScalarField::from_le_bytes_mod_order(&new_hash.into_bigint().to_bytes_le()); + return Ok((z_i1, new_x)); + } + + } + + /// A function compute public IO of an instance: u.x = hash(i, z0, zi, Ui). + fn hash_io( + i: BaseField, + z_i: &State, + z_i1: &State, + big_u: &FInstance + ) -> BaseField { + let mut hasher = T::default(); + i.serialize_uncompressed(&mut hasher).unwrap(); + z_i.state.serialize_uncompressed(&mut hasher).unwrap(); + z_i1.state.serialize_uncompressed(&mut hasher).unwrap(); + + big_u.com_e.0.serialize_uncompressed(&mut hasher).unwrap(); + big_u.u.serialize_uncompressed(&mut hasher).unwrap(); + big_u.com_w.0.serialize_uncompressed(&mut hasher).unwrap(); + + for x in &big_u.x { + x.serialize_uncompressed(&mut hasher).unwrap(); + } + + let data = Some(hasher.finalize().to_vec()); + let mut seed: [u8; 8] = Default::default(); + seed.copy_from_slice(&data.clone().unwrap_or_default()[0..8]); + let seed = u64::from_le_bytes(seed); + let mut rng = StdRng::seed_from_u64(seed); + BaseField::rand(&mut rng) + } +} + diff --git a/nova/src/ivc/mod.rs b/nova/src/ivc/mod.rs new file mode 100644 index 0000000..4d47a1c --- /dev/null +++ b/nova/src/ivc/mod.rs @@ -0,0 +1,15 @@ +use crate::nifs::{NIFSProof}; +use crate::r1cs::{FInstance}; + +pub struct IVCProof{ + u: FInstance, + u_proof: NIFSProof, + big_u: FInstance, + big_u_proof: NIFSProof, +} + +// pub struct IVC { +// srs: Srs, +// augmented_circuit: AugmentedCircuit, +// } + diff --git a/nova/src/lib.rs b/nova/src/lib.rs index 987834f..c3f5a7e 100644 --- a/nova/src/lib.rs +++ b/nova/src/lib.rs @@ -1,5 +1,6 @@ mod nifs; mod utils; mod transcript; +mod ivc; mod circuit; mod r1cs; \ No newline at end of file From 963fa94ba23bc1ac41507406034237363d0a57a1 Mon Sep 17 00:00:00 2001 From: vanhger Date: Sun, 12 May 2024 20:43:44 +0700 Subject: [PATCH 08/18] Reformat NIFS code & Add ZkIVCProof to AugmentedCircuit function --- nova/src/circuit.rs | 73 ++++++++++++++++++---------------- nova/src/ivc/mod.rs | 37 ++++++++++++----- nova/src/nifs/mod.rs | 43 +------------------- nova/src/nifs/nifs_prover.rs | 35 +++++++++++++++- nova/src/nifs/nifs_verifier.rs | 11 ++++- 5 files changed, 111 insertions(+), 88 deletions(-) diff --git a/nova/src/circuit.rs b/nova/src/circuit.rs index e426ad7..5728d8b 100644 --- a/nova/src/circuit.rs +++ b/nova/src/circuit.rs @@ -7,8 +7,8 @@ use ark_std::UniformRand; use rand::prelude::StdRng; use rand::SeedableRng; use sha2::Digest; -use kzg::commitment::KzgCommitment; use kzg::types::{BaseField, ScalarField}; +use crate::ivc::ZkIVCProof; use crate::nifs::{NIFS}; use crate::r1cs::{FInstance, FWitness}; use crate::transcript::Transcript; @@ -21,42 +21,38 @@ pub type ConstraintF = <::BaseField as Field>::BasePrimeFiel pub struct State { pub state: BaseField, } -pub struct FCircuit { - // state u_i - u_i: State, - // state u_{i+1} - u_i1: State, -} - -impl FCircuit { - pub fn run(&self, fwitness: &FWitness) { - } +/// trait for F circuit +pub trait FCircuit { + // return state z_{i+1} = F(z_i, w_i) + fn run(&self, z_i: &State, w_i: &FWitness) -> State; } + /// F' circuit -pub struct AugmentedCircuit { - pub f_circuit: FCircuit, +pub struct AugmentedCircuit> { + pub f_circuit: FC, pub i: BaseField, pub trivial_instance: FInstance, // trivial instance. pub z_0: State, pub z_i: State, - - phantom_data: PhantomData, + phantom_data_t: PhantomData, + phantom_data_f: PhantomData, } -impl AugmentedCircuit { +impl > AugmentedCircuit { pub fn run( &self, - u_i: &FInstance, + ivc_proof: &ZkIVCProof, w_i: &FWitness, - big_u_i: &FInstance, - r: Option, - com_t: Option<&KzgCommitment>, transcript: &mut Transcript, ) -> Result<(State, ScalarField), String>{ + + let mut z_i1; + let mut new_x; + // compute hash(i, z_0, z_i, U_i) - let hash_x = Self::hash_io(self.i, &self.z_0, &self.z_i, big_u_i); + let hash_x = Self::hash_io(self.i, &self.z_0, &self.z_i, &ivc_proof.big_u_i); if self.i != BaseField::from(0) { @@ -64,49 +60,58 @@ impl AugmentedCircuit { // Because u_i.x is in ScalarField while hash is in BaseField, they need to // be converted into a comparable type // Todo: Non-native field transform - let u_dot_x = u_i.x[0].clone(); + let u_dot_x = ivc_proof.u_i.x[0].clone(); let hash_fr = ScalarField::from_le_bytes_mod_order(&hash_x.into_bigint().to_bytes_le()); if u_dot_x != hash_fr { return Err(String::from("Public IO is wrong ")); } // 2. check that u_i.comE = com_([0,...]) and u_i.u = 1 - if u_i.com_e != self.trivial_instance.com_e { + if ivc_proof.u_i.com_e != self.trivial_instance.com_e { return Err(String::from("Commitment of E is wrong")); } - if u_i.u.is_one() { + if ivc_proof.u_i.u.is_one() { return Err(String::from("Commitment of E is wrong")); } // 3. verify challenge r - let challenge_checker = NIFS::::verify_challenge(r.unwrap(), u_i.u, big_u_i.u, com_t.unwrap(), transcript); + let r = ivc_proof.folded_u_proof.clone().unwrap().r; + let com_t = ivc_proof.com_t.clone().unwrap(); + + let challenge_checker = NIFS::::verify_challenge(r, ivc_proof.u_i.u, ivc_proof.big_u_i.u, &com_t, transcript); if challenge_checker.is_err() { return Err(challenge_checker.unwrap_err()); } // 3.compute U_{i+1} - let big_u_i1 = NIFS::::verifier(r.unwrap(), u_i, big_u_i, com_t.unwrap()); + let big_u_i1 = NIFS::::verifier(r, &ivc_proof.u_i, &ivc_proof.big_u_i, &com_t); // compute z_{i+1} = F(z_i, w_i) - self.f_circuit.run(w_i); - let z_i1 = self.f_circuit.u_i1.clone(); + z_i1 = self.f_circuit.run(&self.z_i, w_i); // compute hash let mut new_hash= Self::hash_io(self.i.add(BaseField::one()), &self.z_0, &z_i1, &big_u_i1); // convert into ScalarField - let new_x = ScalarField::from_le_bytes_mod_order(&new_hash.into_bigint().to_bytes_le()); - return Ok((z_i1, new_x)); + new_x = ScalarField::from_le_bytes_mod_order(&new_hash.into_bigint().to_bytes_le()); + } else { // i == 0 // compute z_1 = F(z_0, w_i) - self.f_circuit.run(w_i); - let z_i1 = self.f_circuit.u_i1.clone(); + z_i1 = self.f_circuit.run(&self.z_i, w_i); + + // compute hash let new_hash = Self::hash_io(BaseField::one(), &self.z_0, &z_i1, &self.trivial_instance); // convert into ScalarField - let new_x = ScalarField::from_le_bytes_mod_order(&new_hash.into_bigint().to_bytes_le()); - return Ok((z_i1, new_x)); + new_x = ScalarField::from_le_bytes_mod_order(&new_hash.into_bigint().to_bytes_le()); + } + return Ok((z_i1, new_x)); + + } + pub fn next_state(&mut self, z_i1: &State) { + self.z_i = z_i1.clone(); + self.i = self.i + BaseField::one(); } /// A function compute public IO of an instance: u.x = hash(i, z0, zi, Ui). diff --git a/nova/src/ivc/mod.rs b/nova/src/ivc/mod.rs index 4d47a1c..d58f0c3 100644 --- a/nova/src/ivc/mod.rs +++ b/nova/src/ivc/mod.rs @@ -1,15 +1,32 @@ +mod ivc_prover; + +use ark_ff::PrimeField; +use sha2::Digest; +use kzg::commitment::KzgCommitment; +use kzg::scheme::KzgScheme; +use crate::circuit::{AugmentedCircuit, FCircuit}; use crate::nifs::{NIFSProof}; -use crate::r1cs::{FInstance}; +use crate::r1cs::{FInstance, FWitness}; +use crate::transcript::Transcript; + +/// zero knowledge proof for IVC +pub struct ZkIVCProof { + pub u_i: FInstance, + pub big_u_i: FInstance, + pub com_t: Option, + pub folded_u_proof: Option, +} -pub struct IVCProof{ - u: FInstance, - u_proof: NIFSProof, - big_u: FInstance, - big_u_proof: NIFSProof, +pub struct IVCProof { + pub u_i: FInstance, + pub w_i: FWitness, + pub big_u_i: FInstance, + pub big_w_i: FWitness, } -// pub struct IVC { -// srs: Srs, -// augmented_circuit: AugmentedCircuit, -// } +pub struct IVC > { + scheme: KzgScheme, + augmented_circuit: AugmentedCircuit, + ivc_transcript: Transcript, +} diff --git a/nova/src/nifs/mod.rs b/nova/src/nifs/mod.rs index 23b7702..fd67727 100644 --- a/nova/src/nifs/mod.rs +++ b/nova/src/nifs/mod.rs @@ -3,17 +3,16 @@ use std::ops::Mul; use ark_ec::CurveGroup; use kzg::commitment::KzgCommitment; -use kzg::scheme::KzgScheme; use kzg::types::ScalarField; use sha2::{Digest}; use kzg::opening::KzgOpening; use crate::r1cs::{FInstance, FWitness, R1CS}; -use crate::transcript::Transcript; use crate::utils::{hadamard_product, matrix_vector_product, vec_add, vec_sub, vector_elem_product}; mod nifs_verifier; mod nifs_prover; +#[derive(Clone)] pub struct NIFSProof { pub r: ScalarField, pub opening_point: ScalarField, @@ -99,45 +98,5 @@ impl NIFS { } } - pub fn prover( - r1cs: &R1CS, - fw1: &FWitness, - fw2: &FWitness, - fi1: &FInstance, - fi2: &FInstance, - scheme: &KzgScheme, - transcript: &mut Transcript - ) -> (FWitness, FInstance, KzgCommitment, ScalarField) { - let mut z1 = fw1.w.clone(); - z1.append(&mut fi1.x.clone()); - z1.push(fi1.u); - - let mut z2 = fw2.w.clone(); - z2.append(&mut fi2.x.clone()); - z2.push(fi2.u); - - let t = NIFS::::compute_t(&r1cs, fi1.u, fi2.u, &z1, &z2); - let com_t = scheme.commit_vector(&t); - - transcript.feed_scalar_num(fi1.u); - transcript.feed_scalar_num(fi2.u); - transcript.feed(&com_t); - let [r] = transcript.generate_challenges(); - - let new_witness = NIFS::::fold_witness(r, fw1, fw2, &t); - let new_instance = NIFS::::fold_instance(r, fi1, fi2, &com_t); - - (new_witness, new_instance, com_t, r) - } - - pub fn verifier( - r: ScalarField, - fi1: &FInstance, - fi2: &FInstance, - com_t: &KzgCommitment, - ) -> FInstance { - NIFS::::fold_instance(r, fi1, fi2, com_t) - } - } diff --git a/nova/src/nifs/nifs_prover.rs b/nova/src/nifs/nifs_prover.rs index 2a9b924..d366a56 100644 --- a/nova/src/nifs/nifs_prover.rs +++ b/nova/src/nifs/nifs_prover.rs @@ -1,12 +1,45 @@ use sha2::Digest; -use kzg::opening::KzgOpening; +use kzg::commitment::KzgCommitment; use kzg::scheme::KzgScheme; use kzg::types::ScalarField; use crate::nifs::{FInstance, FWitness, NIFS, NIFSProof}; +use crate::r1cs::R1CS; use crate::transcript::Transcript; impl NIFS { + + pub fn prover( + r1cs: &R1CS, + fw1: &FWitness, + fw2: &FWitness, + fi1: &FInstance, + fi2: &FInstance, + scheme: &KzgScheme, + transcript: &mut Transcript + ) -> (FWitness, FInstance, KzgCommitment, ScalarField) { + let mut z1 = fw1.w.clone(); + z1.append(&mut fi1.x.clone()); + z1.push(fi1.u); + + let mut z2 = fw2.w.clone(); + z2.append(&mut fi2.x.clone()); + z2.push(fi2.u); + + let t = NIFS::::compute_t(&r1cs, fi1.u, fi2.u, &z1, &z2); + let com_t = scheme.commit_vector(&t); + + transcript.feed_scalar_num(fi1.u); + transcript.feed_scalar_num(fi2.u); + transcript.feed(&com_t); + let [r] = transcript.generate_challenges(); + + let new_witness = NIFS::::fold_witness(r, fw1, fw2, &t); + let new_instance = NIFS::::fold_instance(r, fi1, fi2, &com_t); + + (new_witness, new_instance, com_t, r) + } + pub fn prove( r: ScalarField, fw: &FWitness, diff --git a/nova/src/nifs/nifs_verifier.rs b/nova/src/nifs/nifs_verifier.rs index 27ac3a1..b98da89 100644 --- a/nova/src/nifs/nifs_verifier.rs +++ b/nova/src/nifs/nifs_verifier.rs @@ -3,13 +3,22 @@ use sha2::Digest; use kzg::commitment::KzgCommitment; use kzg::scheme::KzgScheme; use kzg::types::ScalarField; -use plonk::prover::Proof; use crate::nifs::{FInstance, NIFS, R1CS, NIFSProof}; use crate::transcript::Transcript; use crate::utils::{to_f_matrix, to_f_vec}; impl NIFS { + + pub fn verifier( + r: ScalarField, + fi1: &FInstance, + fi2: &FInstance, + com_t: &KzgCommitment, + ) -> FInstance { + NIFS::::fold_instance(r, fi1, fi2, com_t) + } + pub fn verify( proof: &NIFSProof, fi1: &FInstance, From b1599b8683d599ff162a2dbffc1e4302c83eae69 Mon Sep 17 00:00:00 2001 From: vanhger Date: Sun, 12 May 2024 22:24:32 +0700 Subject: [PATCH 09/18] Implement the 'prove' function for IVC. --- nova/src/circuit.rs | 18 +++++++------- nova/src/ivc/ivc_prover.rs | 49 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 9 deletions(-) create mode 100644 nova/src/ivc/ivc_prover.rs diff --git a/nova/src/circuit.rs b/nova/src/circuit.rs index 5728d8b..40b2019 100644 --- a/nova/src/circuit.rs +++ b/nova/src/circuit.rs @@ -43,7 +43,7 @@ pub struct AugmentedCircuit > AugmentedCircuit { pub fn run( &self, - ivc_proof: &ZkIVCProof, + zk_ivc_proof: &ZkIVCProof, w_i: &FWitness, transcript: &mut Transcript, ) -> Result<(State, ScalarField), String>{ @@ -52,7 +52,7 @@ impl let mut new_x; // compute hash(i, z_0, z_i, U_i) - let hash_x = Self::hash_io(self.i, &self.z_0, &self.z_i, &ivc_proof.big_u_i); + let hash_x = Self::hash_io(self.i, &self.z_0, &self.z_i, &zk_ivc_proof.big_u_i); if self.i != BaseField::from(0) { @@ -60,31 +60,31 @@ impl // Because u_i.x is in ScalarField while hash is in BaseField, they need to // be converted into a comparable type // Todo: Non-native field transform - let u_dot_x = ivc_proof.u_i.x[0].clone(); + let u_dot_x = zk_ivc_proof.u_i.x[0].clone(); let hash_fr = ScalarField::from_le_bytes_mod_order(&hash_x.into_bigint().to_bytes_le()); if u_dot_x != hash_fr { return Err(String::from("Public IO is wrong ")); } // 2. check that u_i.comE = com_([0,...]) and u_i.u = 1 - if ivc_proof.u_i.com_e != self.trivial_instance.com_e { + if zk_ivc_proof.u_i.com_e != self.trivial_instance.com_e { return Err(String::from("Commitment of E is wrong")); } - if ivc_proof.u_i.u.is_one() { + if zk_ivc_proof.u_i.u.is_one() { return Err(String::from("Commitment of E is wrong")); } // 3. verify challenge r - let r = ivc_proof.folded_u_proof.clone().unwrap().r; - let com_t = ivc_proof.com_t.clone().unwrap(); + let r = zk_ivc_proof.folded_u_proof.clone().unwrap().r; + let com_t = zk_ivc_proof.com_t.clone().unwrap(); - let challenge_checker = NIFS::::verify_challenge(r, ivc_proof.u_i.u, ivc_proof.big_u_i.u, &com_t, transcript); + let challenge_checker = NIFS::::verify_challenge(r, zk_ivc_proof.u_i.u, zk_ivc_proof.big_u_i.u, &com_t, transcript); if challenge_checker.is_err() { return Err(challenge_checker.unwrap_err()); } // 3.compute U_{i+1} - let big_u_i1 = NIFS::::verifier(r, &ivc_proof.u_i, &ivc_proof.big_u_i, &com_t); + let big_u_i1 = NIFS::::verifier(r, &zk_ivc_proof.u_i, &zk_ivc_proof.big_u_i, &com_t); // compute z_{i+1} = F(z_i, w_i) z_i1 = self.f_circuit.run(&self.z_i, w_i); diff --git a/nova/src/ivc/ivc_prover.rs b/nova/src/ivc/ivc_prover.rs new file mode 100644 index 0000000..dbf0b1b --- /dev/null +++ b/nova/src/ivc/ivc_prover.rs @@ -0,0 +1,49 @@ +use ark_ff::{PrimeField, Zero}; +use sha2::Digest; +use kzg::types::BaseField; +use crate::circuit::{FCircuit}; +use crate::ivc::{IVC, IVCProof, ZkIVCProof}; +use crate::nifs::NIFS; +use crate::r1cs::R1CS; +use crate::transcript::Transcript; + +impl > IVC { + pub fn prove( + &self, + prover_transcript: &mut Transcript, + r1cs: &R1CS, + i: BaseField, + ivc_proof: &IVCProof, + ) -> ZkIVCProof { + + if ! i.is_zero() { + let (big_w_out, big_u_out, com_t, r) = NIFS::::prover( + r1cs, + &ivc_proof.w_i, + &ivc_proof.big_w_i, + &ivc_proof.u_i, + &ivc_proof.big_u_i, + &self.scheme, + prover_transcript, + ); + + let nifs_proof = NIFS::::prove(r, &big_w_out, &big_u_out, &self.scheme, prover_transcript); + + ZkIVCProof { + u_i: ivc_proof.u_i.clone(), + big_u_i: ivc_proof.big_u_i.clone(), + com_t: Some(com_t), + folded_u_proof: Some(nifs_proof) + } + } else { + ZkIVCProof { + u_i: ivc_proof.u_i.clone(), + big_u_i: ivc_proof.big_u_i.clone(), + com_t: None, + folded_u_proof: None + } + } + } + + +} \ No newline at end of file From 07b5f9c943e02a9c895daeeb087538a2ca2ac5bb Mon Sep 17 00:00:00 2001 From: vanhger Date: Mon, 13 May 2024 18:10:30 +0700 Subject: [PATCH 10/18] Implement the 'verify' function for IVC. --- nova/src/circuit.rs | 19 ++++------- nova/src/ivc/ivc_prover.rs | 8 ++--- nova/src/ivc/ivc_verifier.rs | 62 ++++++++++++++++++++++++++++++++++++ nova/src/ivc/mod.rs | 1 + nova/src/nifs/mod.rs | 2 +- nova/src/r1cs/mod.rs | 25 ++++++++++++++- 6 files changed, 97 insertions(+), 20 deletions(-) create mode 100644 nova/src/ivc/ivc_verifier.rs diff --git a/nova/src/circuit.rs b/nova/src/circuit.rs index 40b2019..f9c2297 100644 --- a/nova/src/circuit.rs +++ b/nova/src/circuit.rs @@ -3,9 +3,6 @@ use std::ops::Add; use ark_ec::CurveGroup; use ark_ff::{BigInteger, Field, One, PrimeField}; use ark_serialize::CanonicalSerialize; -use ark_std::UniformRand; -use rand::prelude::StdRng; -use rand::SeedableRng; use sha2::Digest; use kzg::types::{BaseField, ScalarField}; use crate::ivc::ZkIVCProof; @@ -71,7 +68,7 @@ impl return Err(String::from("Commitment of E is wrong")); } if zk_ivc_proof.u_i.u.is_one() { - return Err(String::from("Commitment of E is wrong")); + return Err(String::from("Scalar u is wrong")); } // 3. verify challenge r @@ -115,16 +112,16 @@ impl } /// A function compute public IO of an instance: u.x = hash(i, z0, zi, Ui). - fn hash_io( + pub fn hash_io( i: BaseField, + z_0: &State, z_i: &State, - z_i1: &State, big_u: &FInstance ) -> BaseField { let mut hasher = T::default(); i.serialize_uncompressed(&mut hasher).unwrap(); + z_0.state.serialize_uncompressed(&mut hasher).unwrap(); z_i.state.serialize_uncompressed(&mut hasher).unwrap(); - z_i1.state.serialize_uncompressed(&mut hasher).unwrap(); big_u.com_e.0.serialize_uncompressed(&mut hasher).unwrap(); big_u.u.serialize_uncompressed(&mut hasher).unwrap(); @@ -134,12 +131,8 @@ impl x.serialize_uncompressed(&mut hasher).unwrap(); } - let data = Some(hasher.finalize().to_vec()); - let mut seed: [u8; 8] = Default::default(); - seed.copy_from_slice(&data.clone().unwrap_or_default()[0..8]); - let seed = u64::from_le_bytes(seed); - let mut rng = StdRng::seed_from_u64(seed); - BaseField::rand(&mut rng) + let data = hasher.finalize().to_vec(); + BaseField::from_le_bytes_mod_order(&data) } } diff --git a/nova/src/ivc/ivc_prover.rs b/nova/src/ivc/ivc_prover.rs index dbf0b1b..7f6eb42 100644 --- a/nova/src/ivc/ivc_prover.rs +++ b/nova/src/ivc/ivc_prover.rs @@ -1,6 +1,6 @@ use ark_ff::{PrimeField, Zero}; use sha2::Digest; -use kzg::types::BaseField; +use kzg::types::{BaseField, ScalarField}; use crate::circuit::{FCircuit}; use crate::ivc::{IVC, IVCProof, ZkIVCProof}; use crate::nifs::NIFS; @@ -11,14 +11,14 @@ impl pub fn prove( &self, prover_transcript: &mut Transcript, - r1cs: &R1CS, + r1cs: &R1CS, i: BaseField, ivc_proof: &IVCProof, ) -> ZkIVCProof { if ! i.is_zero() { let (big_w_out, big_u_out, com_t, r) = NIFS::::prover( - r1cs, + &r1cs, &ivc_proof.w_i, &ivc_proof.big_w_i, &ivc_proof.u_i, @@ -44,6 +44,4 @@ impl } } } - - } \ No newline at end of file diff --git a/nova/src/ivc/ivc_verifier.rs b/nova/src/ivc/ivc_verifier.rs new file mode 100644 index 0000000..6d8c3df --- /dev/null +++ b/nova/src/ivc/ivc_verifier.rs @@ -0,0 +1,62 @@ +use ark_ff::{BigInteger, One, PrimeField, Zero}; +use sha2::Digest; +use kzg::types::{BaseField, ScalarField}; +use crate::circuit::{AugmentedCircuit, FCircuit, State}; +use crate::ivc::{IVC, ZkIVCProof}; +use crate::nifs::NIFS; + +impl > IVC { + pub fn verify( + &mut self, + i: BaseField, + z_0: &State, + z_i: &State, + zk_ivc_proof: ZkIVCProof + ) -> Result<(), String> { + if i == BaseField::zero() { + return if z_0.state == z_i.state { + Ok(()) + } else { + Err(String::from("Verify failed: wrong state")) + } + } else { + + // 1. parse zkIVCProof = (U, u, comT, nifs_proof) + let u_i = zk_ivc_proof.u_i; + let big_u_i = zk_ivc_proof.big_u_i; + if zk_ivc_proof.com_t.is_none() { + return Err(String::from("Verify failed: commitment of cross term T is wrong")); + } + + if zk_ivc_proof.folded_u_proof.is_none() { + return Err(String::from("Verify failed: commitment of cross term T is wrong")); + } + let com_t = zk_ivc_proof.com_t.unwrap(); + let folded_u_proof = zk_ivc_proof.folded_u_proof.unwrap(); + + // 2. check that u.x = hash(i, z_0, z_i, U) + let hash_io = AugmentedCircuit::::hash_io(i, z_0, z_i, &big_u_i); + let hash_fr = ScalarField::from_le_bytes_mod_order(&hash_io.into_bigint().to_bytes_le()); + if u_i.x[0] != hash_fr { + return Err(String::from("Verify failed: Public IO is wrong")); + } + + // 3. check that u_i.comE = com_([0,...]) and u_i.u = 1 + if u_i.com_e != self.augmented_circuit.trivial_instance.com_e { + return Err(String::from("Verify failed: Commitment of E is wrong")); + } + if u_i.u.is_one() { + return Err(String::from("Verify failed: Scalar u is wrong")); + } + + // 4. compute U' = NIFS.V(U, u, comT) + let big_u_out = NIFS::::verifier(folded_u_proof.r, &u_i, &big_u_i, &com_t); + + // 5. verify that zkSNARK.V(U', pi_U') = 1 + let res = NIFS::::verify(&folded_u_proof, &u_i, &big_u_i, &big_u_out, &com_t, &self.scheme, &mut self.ivc_transcript); + + res + + } + } +} \ No newline at end of file diff --git a/nova/src/ivc/mod.rs b/nova/src/ivc/mod.rs index d58f0c3..9f0b90d 100644 --- a/nova/src/ivc/mod.rs +++ b/nova/src/ivc/mod.rs @@ -1,4 +1,5 @@ mod ivc_prover; +mod ivc_verifier; use ark_ff::PrimeField; use sha2::Digest; diff --git a/nova/src/nifs/mod.rs b/nova/src/nifs/mod.rs index fd67727..85fa54b 100644 --- a/nova/src/nifs/mod.rs +++ b/nova/src/nifs/mod.rs @@ -9,7 +9,7 @@ use kzg::opening::KzgOpening; use crate::r1cs::{FInstance, FWitness, R1CS}; use crate::utils::{hadamard_product, matrix_vector_product, vec_add, vec_sub, vector_elem_product}; -mod nifs_verifier; +pub(crate) mod nifs_verifier; mod nifs_prover; #[derive(Clone)] diff --git a/nova/src/r1cs/mod.rs b/nova/src/r1cs/mod.rs index 04067cd..343745c 100644 --- a/nova/src/r1cs/mod.rs +++ b/nova/src/r1cs/mod.rs @@ -16,7 +16,7 @@ pub struct R1CS { /// Create Committed Relaxed R1CS Instance structure with KZG commitment /// Todo: Need to impl a general-curve commitment. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct FInstance { pub com_e: KzgCommitment, pub u: ScalarField, @@ -104,3 +104,26 @@ pub fn is_satis_relaxed( } } + + +#[cfg(test)] + +mod tests { + use kzg::scheme::KzgScheme; + use kzg::srs::Srs; + use kzg::types::ScalarField; + use crate::nifs::nifs_verifier::gen_test_values; + + #[test] + pub fn test_r1cs() { + let (r1cs, witnesses, x) = gen_test_values::(2); + let (matrix_a, _, _) = (r1cs.matrix_a.clone(), r1cs.matrix_b.clone(), r1cs.matrix_c.clone()); + + // Trusted setup + let domain_size = witnesses[0].len() + x[0].len() + 1; + let srs = Srs::new(domain_size); + let scheme = KzgScheme::new(srs); + + + } +} \ No newline at end of file From 78a7caccaac9d3aaba919ada4df5f0dbe7beefb1 Mon Sep 17 00:00:00 2001 From: vanhger Date: Tue, 14 May 2024 12:28:09 +0700 Subject: [PATCH 11/18] Change the parameters of circuit. --- nova/src/circuit.rs | 91 +++++++++++++++++++--------------- nova/src/nifs/nifs_verifier.rs | 13 +++-- nova/src/r1cs/mod.rs | 4 +- 3 files changed, 59 insertions(+), 49 deletions(-) diff --git a/nova/src/circuit.rs b/nova/src/circuit.rs index f9c2297..eabb387 100644 --- a/nova/src/circuit.rs +++ b/nova/src/circuit.rs @@ -4,8 +4,8 @@ use ark_ec::CurveGroup; use ark_ff::{BigInteger, Field, One, PrimeField}; use ark_serialize::CanonicalSerialize; use sha2::Digest; +use kzg::commitment::KzgCommitment; use kzg::types::{BaseField, ScalarField}; -use crate::ivc::ZkIVCProof; use crate::nifs::{NIFS}; use crate::r1cs::{FInstance, FWitness}; use crate::transcript::Transcript; @@ -14,101 +14,112 @@ pub type ConstraintF = <::BaseField as Field>::BasePrimeFiel /// State structure of IVC, which is presented in BaseField of Bsn12_381 curve /// Todo: Implement a general version. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct State { pub state: BaseField, } /// trait for F circuit -pub trait FCircuit { +pub trait FCircuit { // return state z_{i+1} = F(z_i, w_i) fn run(&self, z_i: &State, w_i: &FWitness) -> State; } /// F' circuit -pub struct AugmentedCircuit> { +pub struct AugmentedCircuit { pub f_circuit: FC, pub i: BaseField, pub trivial_instance: FInstance, // trivial instance. pub z_0: State, pub z_i: State, - phantom_data_t: PhantomData, - phantom_data_f: PhantomData, + pub hash_x: Option, + pub phantom_data_t: PhantomData, } -impl > AugmentedCircuit { +impl AugmentedCircuit { pub fn run( - &self, - zk_ivc_proof: &ZkIVCProof, + &mut self, + u_i: &FInstance, + big_u_i: Option<&FInstance>, w_i: &FWitness, - transcript: &mut Transcript, - ) -> Result<(State, ScalarField), String>{ + com_t: Option<&KzgCommitment>, + ) -> Result{ - let mut z_i1; - let mut new_x; + if self.i != BaseField::from(0) { - // compute hash(i, z_0, z_i, U_i) - let hash_x = Self::hash_io(self.i, &self.z_0, &self.z_i, &zk_ivc_proof.big_u_i); + // check that if i > 0 then U_i and com_t must exist + if big_u_i.is_none() || com_t.is_none() { + return Err(String::from("Wrong parameters.")); + } - if self.i != BaseField::from(0) { + // check that if i > 0 then the hash_x must exist + if self.hash_x.is_none() { + return Err(String::from("The hash public IO must exist")) + } - // 1. check that u.x =? hash - // Because u_i.x is in ScalarField while hash is in BaseField, they need to + // get hash_x + let hash_x = self.hash_x.clone().unwrap(); + + // 1. check that u.x =? hash_x + // Because u_i.x is in ScalarField while hash_x is in BaseField, they need to // be converted into a comparable type // Todo: Non-native field transform - let u_dot_x = zk_ivc_proof.u_i.x[0].clone(); + + let u_dot_x = u_i.x[0].clone(); let hash_fr = ScalarField::from_le_bytes_mod_order(&hash_x.into_bigint().to_bytes_le()); if u_dot_x != hash_fr { return Err(String::from("Public IO is wrong ")); } // 2. check that u_i.comE = com_([0,...]) and u_i.u = 1 - if zk_ivc_proof.u_i.com_e != self.trivial_instance.com_e { + if u_i.com_e != self.trivial_instance.com_e { return Err(String::from("Commitment of E is wrong")); } - if zk_ivc_proof.u_i.u.is_one() { + + if u_i.u != ScalarField::one() { return Err(String::from("Scalar u is wrong")); } - // 3. verify challenge r - let r = zk_ivc_proof.folded_u_proof.clone().unwrap().r; - let com_t = zk_ivc_proof.com_t.clone().unwrap(); - - let challenge_checker = NIFS::::verify_challenge(r, zk_ivc_proof.u_i.u, zk_ivc_proof.big_u_i.u, &com_t, transcript); - if challenge_checker.is_err() { - return Err(challenge_checker.unwrap_err()); - } + // 3. recreate challenge r + let mut transcript = Transcript::::default(); + transcript.feed_scalar_num(u_i.u); + transcript.feed_scalar_num(big_u_i.unwrap().u); + transcript.feed(com_t.unwrap()); + let [r] = transcript.generate_challenges(); // 3.compute U_{i+1} - let big_u_i1 = NIFS::::verifier(r, &zk_ivc_proof.u_i, &zk_ivc_proof.big_u_i, &com_t); + let big_u_i1 = NIFS::::verifier(r, u_i, big_u_i.unwrap(), com_t.unwrap()); // compute z_{i+1} = F(z_i, w_i) - z_i1 = self.f_circuit.run(&self.z_i, w_i); + let z_i1 = self.f_circuit.run(&self.z_i, w_i); // compute hash - let mut new_hash= Self::hash_io(self.i.add(BaseField::one()), &self.z_0, &z_i1, &big_u_i1); - // convert into ScalarField - new_x = ScalarField::from_le_bytes_mod_order(&new_hash.into_bigint().to_bytes_le()); + let new_hash= Self::hash_io(self.i.add(BaseField::one()), &self.z_0, &z_i1, &big_u_i1); + + // update augmented F function to the next step + self.next_step(&z_i1, new_hash); } else { // i == 0 // compute z_1 = F(z_0, w_i) - z_i1 = self.f_circuit.run(&self.z_i, w_i); + let z_i1 = self.f_circuit.run(&self.z_i, w_i); // compute hash let new_hash = Self::hash_io(BaseField::one(), &self.z_0, &z_i1, &self.trivial_instance); - // convert into ScalarField - new_x = ScalarField::from_le_bytes_mod_order(&new_hash.into_bigint().to_bytes_le()); - } + // update augmented F function to the next step + self.next_step(&z_i1, new_hash); - return Ok((z_i1, new_x)); + } + return Ok(self.hash_x.unwrap()); } - pub fn next_state(&mut self, z_i1: &State) { + + fn next_step(&mut self, z_i1: &State, hash_x: BaseField) { self.z_i = z_i1.clone(); self.i = self.i + BaseField::one(); + self.hash_x = Some(hash_x); } /// A function compute public IO of an instance: u.x = hash(i, z0, zi, Ui). diff --git a/nova/src/nifs/nifs_verifier.rs b/nova/src/nifs/nifs_verifier.rs index b98da89..c8525f1 100644 --- a/nova/src/nifs/nifs_verifier.rs +++ b/nova/src/nifs/nifs_verifier.rs @@ -51,14 +51,14 @@ impl NIFS { com_t: &KzgCommitment, transcript: &mut Transcript ) -> Result<(), String> { - // recreate folded instance + // Recreate challenge r transcript.feed_scalar_num(fi1_u); transcript.feed_scalar_num(fi2_u); transcript.feed(&com_t); - // Verify that proof.r = Transcript(fi1.u, fi2.u, cmT) + let [new_r] = transcript.generate_challenges(); - // verify challenge r + // Verify that proof.r = Transcript(fi1.u, fi2.u, cmT) if new_r != r { return Err(String::from("Verify: Error in computing random r")) } @@ -92,7 +92,7 @@ impl NIFS { } } -pub fn gen_test_values(n: usize) -> (R1CS, Vec>, Vec>) { +pub fn gen_test_values(inputs: Vec) -> (R1CS, Vec>, Vec>) { // R1CS for: x^3 + x + 5 = y (example from article // https://vitalik.eth.limo/general/2016/12/10/qap.html ) @@ -118,8 +118,7 @@ pub fn gen_test_values(n: usize) -> (R1CS, Vec>, Vec> = Vec::new(); let mut x: Vec> = Vec::new(); - for i in 0..n { - let input = 3 + i; + for input in inputs { let w_i = to_f_vec::(vec![ 1, input, @@ -150,7 +149,7 @@ mod tests { #[test] fn test_one_fold() { // generate R1CS, witnesses and public input, output. - let (r1cs, witnesses, x) = gen_test_values(2); + let (r1cs, witnesses, x) = gen_test_values(vec![3, 4]); let (matrix_a, _, _) = (r1cs.matrix_a.clone(), r1cs.matrix_b.clone(), r1cs.matrix_c.clone()); // Trusted setup diff --git a/nova/src/r1cs/mod.rs b/nova/src/r1cs/mod.rs index 343745c..da8ad8e 100644 --- a/nova/src/r1cs/mod.rs +++ b/nova/src/r1cs/mod.rs @@ -44,7 +44,7 @@ impl FWitness { } } - pub fn create_trivial_witness(len: usize) -> Self { + pub fn new_trivial_witness(len: usize) -> Self { FWitness { e: vec![ScalarField::zero(); len], w: vec![ScalarField::zero(); len], @@ -116,7 +116,7 @@ mod tests { #[test] pub fn test_r1cs() { - let (r1cs, witnesses, x) = gen_test_values::(2); + let (r1cs, witnesses, x) = gen_test_values::(vec![3, 4]); let (matrix_a, _, _) = (r1cs.matrix_a.clone(), r1cs.matrix_b.clone(), r1cs.matrix_c.clone()); // Trusted setup From cee3eba5cecb52865704a3c44a0dc80f3bf26344 Mon Sep 17 00:00:00 2001 From: vanhger Date: Tue, 14 May 2024 16:19:01 +0700 Subject: [PATCH 12/18] Write a simple testcase for IVC --- nova/src/circuit.rs | 23 +++-- nova/src/ivc/ivc_prover.rs | 10 +- nova/src/ivc/ivc_verifier.rs | 179 ++++++++++++++++++++++++++++++++--- nova/src/ivc/mod.rs | 6 +- 4 files changed, 186 insertions(+), 32 deletions(-) diff --git a/nova/src/circuit.rs b/nova/src/circuit.rs index eabb387..4aa84ce 100644 --- a/nova/src/circuit.rs +++ b/nova/src/circuit.rs @@ -32,8 +32,10 @@ pub struct AugmentedCircuit, // store the next state, use for update step. pub z_i: State, pub hash_x: Option, + pub hash_x_next: Option, // store the next hash IO, use for update step pub phantom_data_t: PhantomData, } @@ -97,8 +99,10 @@ impl AugmentedCircui // compute hash let new_hash= Self::hash_io(self.i.add(BaseField::one()), &self.z_0, &z_i1, &big_u_i1); - // update augmented F function to the next step - self.next_step(&z_i1, new_hash); + // store the next hash + self.hash_x_next = Some(new_hash); + // store the next state + self.z_i1 = Some(z_i1); } else { // i == 0 @@ -108,18 +112,19 @@ impl AugmentedCircui // compute hash let new_hash = Self::hash_io(BaseField::one(), &self.z_0, &z_i1, &self.trivial_instance); - // update augmented F function to the next step - self.next_step(&z_i1, new_hash); - + // store the next hash + self.hash_x_next = Some(new_hash); + // store the next state + self.z_i1 = Some(z_i1); } - return Ok(self.hash_x.unwrap()); + return Ok(self.hash_x_next.unwrap()); } - fn next_step(&mut self, z_i1: &State, hash_x: BaseField) { - self.z_i = z_i1.clone(); + pub fn next_step(&mut self) { + self.z_i = self.z_i1.clone().unwrap(); self.i = self.i + BaseField::one(); - self.hash_x = Some(hash_x); + self.hash_x = self.hash_x_next; } /// A function compute public IO of an instance: u.x = hash(i, z0, zi, Ui). diff --git a/nova/src/ivc/ivc_prover.rs b/nova/src/ivc/ivc_prover.rs index 7f6eb42..f51ee96 100644 --- a/nova/src/ivc/ivc_prover.rs +++ b/nova/src/ivc/ivc_prover.rs @@ -1,21 +1,21 @@ -use ark_ff::{PrimeField, Zero}; +use ark_ff::{Zero}; use sha2::Digest; -use kzg::types::{BaseField, ScalarField}; +use kzg::types::{ScalarField}; use crate::circuit::{FCircuit}; use crate::ivc::{IVC, IVCProof, ZkIVCProof}; use crate::nifs::NIFS; use crate::r1cs::R1CS; use crate::transcript::Transcript; -impl > IVC { +impl IVC { pub fn prove( &self, - prover_transcript: &mut Transcript, r1cs: &R1CS, - i: BaseField, ivc_proof: &IVCProof, + prover_transcript: &mut Transcript, ) -> ZkIVCProof { + let i = self.augmented_circuit.i; if ! i.is_zero() { let (big_w_out, big_u_out, com_t, r) = NIFS::::prover( &r1cs, diff --git a/nova/src/ivc/ivc_verifier.rs b/nova/src/ivc/ivc_verifier.rs index 6d8c3df..4be58b0 100644 --- a/nova/src/ivc/ivc_verifier.rs +++ b/nova/src/ivc/ivc_verifier.rs @@ -4,15 +4,20 @@ use kzg::types::{BaseField, ScalarField}; use crate::circuit::{AugmentedCircuit, FCircuit, State}; use crate::ivc::{IVC, ZkIVCProof}; use crate::nifs::NIFS; +use crate::transcript::Transcript; -impl > IVC { +impl IVC { pub fn verify( &mut self, - i: BaseField, - z_0: &State, - z_i: &State, - zk_ivc_proof: ZkIVCProof + zk_ivc_proof: &ZkIVCProof, + verifier_transcript: &mut Transcript ) -> Result<(), String> { + + let i = self.augmented_circuit.i; + let z_0 = &self.augmented_circuit.z_0; + let z_i = &self.augmented_circuit.z_i; + + if i == BaseField::zero() { return if z_0.state == z_i.state { Ok(()) @@ -22,20 +27,22 @@ impl } else { // 1. parse zkIVCProof = (U, u, comT, nifs_proof) - let u_i = zk_ivc_proof.u_i; - let big_u_i = zk_ivc_proof.big_u_i; + + let u_i = zk_ivc_proof.u_i.clone(); + let big_u_i = zk_ivc_proof.big_u_i.clone(); if zk_ivc_proof.com_t.is_none() { - return Err(String::from("Verify failed: commitment of cross term T is wrong")); + return Err(String::from("Verify failed: commitment of cross term T must exist")); } if zk_ivc_proof.folded_u_proof.is_none() { - return Err(String::from("Verify failed: commitment of cross term T is wrong")); + return Err(String::from("Verify failed: folding proof must exist")); } - let com_t = zk_ivc_proof.com_t.unwrap(); - let folded_u_proof = zk_ivc_proof.folded_u_proof.unwrap(); + let com_t = zk_ivc_proof.com_t.clone().unwrap(); + let folded_u_proof = zk_ivc_proof.folded_u_proof.clone().unwrap(); // 2. check that u.x = hash(i, z_0, z_i, U) - let hash_io = AugmentedCircuit::::hash_io(i, z_0, z_i, &big_u_i); + println!("z_i, {:?}", z_i); + let hash_io = AugmentedCircuit::::hash_io(i, z_0, z_i, &big_u_i); let hash_fr = ScalarField::from_le_bytes_mod_order(&hash_io.into_bigint().to_bytes_le()); if u_i.x[0] != hash_fr { return Err(String::from("Verify failed: Public IO is wrong")); @@ -45,7 +52,7 @@ impl if u_i.com_e != self.augmented_circuit.trivial_instance.com_e { return Err(String::from("Verify failed: Commitment of E is wrong")); } - if u_i.u.is_one() { + if ! u_i.u.is_one() { return Err(String::from("Verify failed: Scalar u is wrong")); } @@ -53,10 +60,154 @@ impl let big_u_out = NIFS::::verifier(folded_u_proof.r, &u_i, &big_u_i, &com_t); // 5. verify that zkSNARK.V(U', pi_U') = 1 - let res = NIFS::::verify(&folded_u_proof, &u_i, &big_u_i, &big_u_out, &com_t, &self.scheme, &mut self.ivc_transcript); + let res = NIFS::::verify(&folded_u_proof, &u_i, &big_u_i, &big_u_out, &com_t, &self.scheme, verifier_transcript); res } } +} + +#[cfg(test)] + +mod tests { + use std::marker::PhantomData; + use sha2::Sha256; + use kzg::scheme::KzgScheme; + use kzg::srs::Srs; + use crate::ivc::IVCProof; + use crate::nifs::nifs_verifier::gen_test_values; + use super::*; + use crate::r1cs::FWitness; + use crate::transcript::Transcript; + + struct TestCircuit {} + impl FCircuit for TestCircuit { + fn run(&self, z_i: &State, w_i: &FWitness) -> State { + let x = w_i.w[1].clone(); + let res = x * x * x + x + ScalarField::from(5); + // because res is in scalar field, we need to convert it into base_field + let base_res = BaseField::from_le_bytes_mod_order(&res.into_bigint().to_bytes_le()); + + State { + state: z_i.state + base_res + } + } + } + + #[test] + fn test_ivc_step_by_step_1() { + // This test: (x1^3 + x1 + 5) + (x2^3 + x2 + 5) = 108 + // x1 = 3, x2 = 4 + + // generate R1CS, witnesses and public input, output. + let (r1cs, witnesses, x) = gen_test_values::(vec![3, 4]); + let (matrix_a, _, _) = (r1cs.matrix_a.clone(), r1cs.matrix_b.clone(), r1cs.matrix_c.clone()); + + // Trusted setup + let domain_size = witnesses[0].len() + x[0].len() + 1; + let srs = Srs::new(domain_size); + let scheme = KzgScheme::new(srs); + + let w_0 = FWitness::new(&witnesses[0], matrix_a.len()); + let w_1 = FWitness::new(&witnesses[1], matrix_a.len()); + + let u_0 = w_0.commit(&scheme, &x[0]); + let mut u_1 = w_1.commit(&scheme, &x[1]); + + let mut i = BaseField::zero(); + + // generate trivial_instance + let trivial_x = vec![ScalarField::from(0); x[0].len()]; + let trivial_witness = FWitness::new_trivial_witness(witnesses[0].len()); + let trivial_instance = trivial_witness.commit(&scheme, &trivial_x); + + let f_circuit = TestCircuit{}; + + let z_0 = State{state: BaseField::from(0)}; + let z_1 = f_circuit.run(&z_0, &w_0); + let z_2 = f_circuit.run(&z_1, &w_1); + + let mut prover_transcript = Transcript::::default(); + let mut verifier_transcript = Transcript::::default(); + + let mut augmented_circuit = AugmentedCircuit:: { + f_circuit, + i: BaseField::zero(), + trivial_instance: trivial_instance.clone(), + z_0: z_0.clone(), + z_i: z_0.clone(), + z_i1: None, + hash_x: None, + hash_x_next: None, + phantom_data_t: PhantomData + }; + + // generate IVC + let mut ivc = IVC:: { + scheme, + augmented_circuit + }; + + // run F' for the first time + // With i = 0, the instance U_i and commitment com_t do not exist. + let res1 = ivc.augmented_circuit.run( + &u_0, + None, + &w_0, + None, + ); + if res1.is_err() { + println!("Step: {:?}, {:?}", i, res1); + } + + // update for next step + ivc.augmented_circuit.next_step(); + i = i + BaseField::one(); + + let u_1_x = AugmentedCircuit::::hash_io(i, &z_0, &z_1, &trivial_instance); + // convert u_1_x from BaseField into ScalarField + u_1.x = vec![ScalarField::from_le_bytes_mod_order(&u_1_x.into_bigint().to_bytes_le())]; + + // U_1 is trivial instance (via the paper). + // Prover fold u_1 and U_1 into U2. + + let ivc_proof = IVCProof{ + u_i: u_1, + w_i: w_1, + big_u_i: trivial_instance, + big_w_i: trivial_witness, + }; + let zk_ivc_proof = ivc.prove( + &r1cs, + &ivc_proof, + &mut prover_transcript + ); + + + // run F' for the second time + let res2 = ivc.augmented_circuit.run( + &ivc_proof.u_i, + Some(&ivc_proof.big_u_i.clone()), + &ivc_proof.w_i, + Some(&zk_ivc_proof.com_t.clone().unwrap()) + ); + + if res2.is_err() { + println!("Step: {:?}, {:?}", i, res2); + } + + // verifier verify the final step + let result = ivc.verify(&zk_ivc_proof, &mut verifier_transcript); + if result.is_err() { + println!("{:?}", result); + } + assert!(result.is_ok()); + + + ivc.augmented_circuit.next_step(); + // check if the final state is 108. + assert_eq!(ivc.augmented_circuit.z_i.state, BaseField::from(108), "Wrong state"); + + } } \ No newline at end of file diff --git a/nova/src/ivc/mod.rs b/nova/src/ivc/mod.rs index 9f0b90d..c7f7665 100644 --- a/nova/src/ivc/mod.rs +++ b/nova/src/ivc/mod.rs @@ -8,7 +8,6 @@ use kzg::scheme::KzgScheme; use crate::circuit::{AugmentedCircuit, FCircuit}; use crate::nifs::{NIFSProof}; use crate::r1cs::{FInstance, FWitness}; -use crate::transcript::Transcript; /// zero knowledge proof for IVC pub struct ZkIVCProof { @@ -25,9 +24,8 @@ pub struct IVCProof { pub big_w_i: FWitness, } -pub struct IVC > { +pub struct IVC > { scheme: KzgScheme, - augmented_circuit: AugmentedCircuit, - ivc_transcript: Transcript, + augmented_circuit: AugmentedCircuit, } From fece4f6e36708a13dad06e61c7232addaaf3ee96 Mon Sep 17 00:00:00 2001 From: vanhger Date: Tue, 14 May 2024 23:07:49 +0700 Subject: [PATCH 13/18] Rewrite the simple test case into a step-by-step IVC test case --- nova/src/circuit.rs | 7 ++- nova/src/ivc/ivc_prover.rs | 36 ++++++----- nova/src/ivc/ivc_verifier.rs | 115 ++++++++++++++++++++++++++++++----- nova/src/r1cs/mod.rs | 1 + 4 files changed, 130 insertions(+), 29 deletions(-) diff --git a/nova/src/circuit.rs b/nova/src/circuit.rs index 4aa84ce..bf72328 100644 --- a/nova/src/circuit.rs +++ b/nova/src/circuit.rs @@ -62,7 +62,7 @@ impl AugmentedCircui // get hash_x let hash_x = self.hash_x.clone().unwrap(); - + println!("c: hash_x: {:?}", hash_x); // 1. check that u.x =? hash_x // Because u_i.x is in ScalarField while hash_x is in BaseField, they need to // be converted into a comparable type @@ -71,6 +71,8 @@ impl AugmentedCircui let u_dot_x = u_i.x[0].clone(); let hash_fr = ScalarField::from_le_bytes_mod_order(&hash_x.into_bigint().to_bytes_le()); if u_dot_x != hash_fr { + println!("u_dot_x: {:?}", u_dot_x); + println!("hash_fr: {:?}", hash_fr); return Err(String::from("Public IO is wrong ")); } @@ -89,6 +91,9 @@ impl AugmentedCircui transcript.feed_scalar_num(big_u_i.unwrap().u); transcript.feed(com_t.unwrap()); let [r] = transcript.generate_challenges(); + if self.i == BaseField::one() { + println!("c: r is: {:?}", r); + } // 3.compute U_{i+1} let big_u_i1 = NIFS::::verifier(r, u_i, big_u_i.unwrap(), com_t.unwrap()); diff --git a/nova/src/ivc/ivc_prover.rs b/nova/src/ivc/ivc_prover.rs index f51ee96..f1428c0 100644 --- a/nova/src/ivc/ivc_prover.rs +++ b/nova/src/ivc/ivc_prover.rs @@ -4,7 +4,7 @@ use kzg::types::{ScalarField}; use crate::circuit::{FCircuit}; use crate::ivc::{IVC, IVCProof, ZkIVCProof}; use crate::nifs::NIFS; -use crate::r1cs::R1CS; +use crate::r1cs::{FInstance, FWitness, R1CS}; use crate::transcript::Transcript; impl IVC { @@ -13,7 +13,7 @@ impl IVC { r1cs: &R1CS, ivc_proof: &IVCProof, prover_transcript: &mut Transcript, - ) -> ZkIVCProof { + ) -> (FWitness, FInstance, ZkIVCProof) { let i = self.augmented_circuit.i; if ! i.is_zero() { @@ -29,19 +29,27 @@ impl IVC { let nifs_proof = NIFS::::prove(r, &big_w_out, &big_u_out, &self.scheme, prover_transcript); - ZkIVCProof { - u_i: ivc_proof.u_i.clone(), - big_u_i: ivc_proof.big_u_i.clone(), - com_t: Some(com_t), - folded_u_proof: Some(nifs_proof) - } + ( + big_w_out, + big_u_out, + ZkIVCProof { + u_i: ivc_proof.u_i.clone(), + big_u_i: ivc_proof.big_u_i.clone(), + com_t: Some(com_t), + folded_u_proof: Some(nifs_proof) + }) + } else { - ZkIVCProof { - u_i: ivc_proof.u_i.clone(), - big_u_i: ivc_proof.big_u_i.clone(), - com_t: None, - folded_u_proof: None - } + ( + ivc_proof.big_w_i.clone(), + ivc_proof.big_u_i.clone(), + ZkIVCProof { + u_i: ivc_proof.u_i.clone(), + big_u_i: ivc_proof.big_u_i.clone(), + com_t: None, + folded_u_proof: None + }) + } } } \ No newline at end of file diff --git a/nova/src/ivc/ivc_verifier.rs b/nova/src/ivc/ivc_verifier.rs index 4be58b0..46cfc33 100644 --- a/nova/src/ivc/ivc_verifier.rs +++ b/nova/src/ivc/ivc_verifier.rs @@ -97,11 +97,11 @@ mod tests { #[test] fn test_ivc_step_by_step_1() { - // This test: (x1^3 + x1 + 5) + (x2^3 + x2 + 5) = 108 - // x1 = 3, x2 = 4 + // This test: (x0^3 + x0 + 5) + (x1^3 + x1 + 5) + (x2^3 + x2 + 5)= 115 + // x0 = 3, x1 = 4, x2 = 1 // generate R1CS, witnesses and public input, output. - let (r1cs, witnesses, x) = gen_test_values::(vec![3, 4]); + let (r1cs, witnesses, x) = gen_test_values::(vec![3, 4, 1]); let (matrix_a, _, _) = (r1cs.matrix_a.clone(), r1cs.matrix_b.clone(), r1cs.matrix_c.clone()); // Trusted setup @@ -111,10 +111,13 @@ mod tests { let w_0 = FWitness::new(&witnesses[0], matrix_a.len()); let w_1 = FWitness::new(&witnesses[1], matrix_a.len()); + let w_2 = FWitness::new(&witnesses[2], matrix_a.len()); let u_0 = w_0.commit(&scheme, &x[0]); let mut u_1 = w_1.commit(&scheme, &x[1]); + let mut u_2 = w_2.commit(&scheme, &x[2]); + // step i let mut i = BaseField::zero(); // generate trivial_instance @@ -122,8 +125,10 @@ mod tests { let trivial_witness = FWitness::new_trivial_witness(witnesses[0].len()); let trivial_instance = trivial_witness.commit(&scheme, &trivial_x); + // generate f_circuit instance let f_circuit = TestCircuit{}; + // generate state let z_0 = State{state: BaseField::from(0)}; let z_1 = f_circuit.run(&z_0, &w_0); let z_2 = f_circuit.run(&z_1, &w_1); @@ -131,6 +136,7 @@ mod tests { let mut prover_transcript = Transcript::::default(); let mut verifier_transcript = Transcript::::default(); + // create F' let mut augmented_circuit = AugmentedCircuit:: { f_circuit, i: BaseField::zero(), @@ -149,6 +155,25 @@ mod tests { augmented_circuit }; + // initialize IVC proof, zkIVCProof, folded witness and folded instance + let mut ivc_proof = IVCProof { + w_i: trivial_witness.clone(), + u_i: trivial_instance.clone(), + big_w_i: trivial_witness.clone(), + big_u_i: trivial_instance.clone(), + }; + + let mut zk_ivc_proof = ZkIVCProof { + u_i: trivial_instance.clone(), + big_u_i: trivial_instance.clone(), + com_t: None, + folded_u_proof: None, + }; + + let mut folded_witness = trivial_witness.clone(); + let mut folded_instance = trivial_instance.clone(); + + println!("Step 1"); // run F' for the first time // With i = 0, the instance U_i and commitment com_t do not exist. let res1 = ivc.augmented_circuit.run( @@ -157,34 +182,50 @@ mod tests { &w_0, None, ); + if res1.is_err() { println!("Step: {:?}, {:?}", i, res1); } + assert!(res1.is_ok()); + + // verifier verify this step + let result = ivc.verify(&zk_ivc_proof, &mut verifier_transcript); + if result.is_err() { + println!("{:?}", result); + } + assert!(result.is_ok()); // update for next step ivc.augmented_circuit.next_step(); i = i + BaseField::one(); + assert_eq!(ivc.augmented_circuit.z_i.state, BaseField::from(35)); + prover_transcript = Transcript::::default(); + verifier_transcript = Transcript::::default(); + // because all instances above are from F, not F', so we need to do this trick. let u_1_x = AugmentedCircuit::::hash_io(i, &z_0, &z_1, &trivial_instance); // convert u_1_x from BaseField into ScalarField u_1.x = vec![ScalarField::from_le_bytes_mod_order(&u_1_x.into_bigint().to_bytes_le())]; - // U_1 is trivial instance (via the paper). - // Prover fold u_1 and U_1 into U2. + // U_1 is a trivial instance (via the paper). + // Prover fold u_1 and U_1 into U_2. - let ivc_proof = IVCProof{ - u_i: u_1, + // generate IVC proof. + ivc_proof = IVCProof{ + u_i: u_1.clone(), w_i: w_1, - big_u_i: trivial_instance, - big_w_i: trivial_witness, + big_u_i: trivial_instance.clone(), + big_w_i: trivial_witness.clone(), }; - let zk_ivc_proof = ivc.prove( + println!("p: u_i {:?}", u_1); + // generate W_2, U_2 and zkIVCProof via IVC proof + (folded_witness, folded_instance, zk_ivc_proof) = ivc.prove( &r1cs, &ivc_proof, &mut prover_transcript ); - + println!("Step 2"); // run F' for the second time let res2 = ivc.augmented_circuit.run( &ivc_proof.u_i, @@ -196,18 +237,64 @@ mod tests { if res2.is_err() { println!("Step: {:?}, {:?}", i, res2); } + assert!(res2.is_ok()); - // verifier verify the final step + // verifier verify this step let result = ivc.verify(&zk_ivc_proof, &mut verifier_transcript); if result.is_err() { println!("{:?}", result); } assert!(result.is_ok()); - + // update next step ivc.augmented_circuit.next_step(); - // check if the final state is 108. + i = i + BaseField::one(); + prover_transcript = Transcript::::default(); + verifier_transcript = Transcript::::default(); + // check if this state is 108. assert_eq!(ivc.augmented_circuit.z_i.state, BaseField::from(108), "Wrong state"); + + let u_2_x = AugmentedCircuit::::hash_io(i, &z_0, &z_2, &folded_instance); + u_2.x = vec![ScalarField::from_le_bytes_mod_order(&u_2_x.into_bigint().to_bytes_le())]; + ivc_proof = IVCProof { + u_i: u_2, + w_i: w_2, + big_u_i: folded_instance, // U_2 + big_w_i: folded_witness // W_2 + }; + + (folded_witness, folded_instance, zk_ivc_proof) = ivc.prove( + &r1cs, + &ivc_proof, + &mut prover_transcript + ); + + println!("Step 3"); + // run F' for the last time + let res3 = ivc.augmented_circuit.run( + &ivc_proof.u_i, + Some(&ivc_proof.big_u_i.clone()), + &ivc_proof.w_i, + Some(&zk_ivc_proof.com_t.clone().unwrap()) + ); + + if res3.is_err() { + println!("Step: {:?}, {:?}", i, res3); + } + assert!(res3.is_ok()); + + // verifier verify this step + let result = ivc.verify(&zk_ivc_proof, &mut verifier_transcript); + if result.is_err() { + println!("{:?}", result); + } + assert!(result.is_ok()); + + // update next step + ivc.augmented_circuit.next_step(); + i = i + BaseField::one(); + // check if this state is 115. + assert_eq!(ivc.augmented_circuit.z_i.state, BaseField::from(115), "Wrong state"); } } \ No newline at end of file diff --git a/nova/src/r1cs/mod.rs b/nova/src/r1cs/mod.rs index da8ad8e..812539a 100644 --- a/nova/src/r1cs/mod.rs +++ b/nova/src/r1cs/mod.rs @@ -27,6 +27,7 @@ pub struct FInstance { /// Create Committed Relaxed FWitness with KZG commitment /// Todo: Need to implement a general-curve commitment +#[derive(Debug, Clone)] pub struct FWitness { pub e: Vec, // pub rE: ScalarField, From 479568cbd5121531a45b03beab734eb59213e2ba Mon Sep 17 00:00:00 2001 From: vanhger Date: Wed, 15 May 2024 08:37:48 +0700 Subject: [PATCH 14/18] Write an iterable usage test case for IVC --- nova/src/circuit.rs | 27 +++++--- nova/src/ivc/ivc_verifier.rs | 117 ++++++++++++++++++++++++++++++++++- nova/src/ivc/mod.rs | 43 +++++++++++++ nova/src/r1cs/mod.rs | 11 ++++ 4 files changed, 188 insertions(+), 10 deletions(-) diff --git a/nova/src/circuit.rs b/nova/src/circuit.rs index bf72328..8d38676 100644 --- a/nova/src/circuit.rs +++ b/nova/src/circuit.rs @@ -1,7 +1,7 @@ use std::marker::PhantomData; use std::ops::Add; use ark_ec::CurveGroup; -use ark_ff::{BigInteger, Field, One, PrimeField}; +use ark_ff::{BigInteger, Field, One, PrimeField, Zero}; use ark_serialize::CanonicalSerialize; use sha2::Digest; use kzg::commitment::KzgCommitment; @@ -40,6 +40,24 @@ pub struct AugmentedCircuit AugmentedCircuit { + + pub fn new( + f_circuit: FC, + trivial_instance: &FInstance, + z_0: &State, + ) -> Self { + Self { + f_circuit, + i: BaseField::zero(), + trivial_instance: trivial_instance.clone(), + z_0: z_0.clone(), + z_i: z_0.clone(), + z_i1: None, + hash_x: None, + hash_x_next: None, + phantom_data_t: PhantomData + } + } pub fn run( &mut self, u_i: &FInstance, @@ -62,7 +80,7 @@ impl AugmentedCircui // get hash_x let hash_x = self.hash_x.clone().unwrap(); - println!("c: hash_x: {:?}", hash_x); + // 1. check that u.x =? hash_x // Because u_i.x is in ScalarField while hash_x is in BaseField, they need to // be converted into a comparable type @@ -71,8 +89,6 @@ impl AugmentedCircui let u_dot_x = u_i.x[0].clone(); let hash_fr = ScalarField::from_le_bytes_mod_order(&hash_x.into_bigint().to_bytes_le()); if u_dot_x != hash_fr { - println!("u_dot_x: {:?}", u_dot_x); - println!("hash_fr: {:?}", hash_fr); return Err(String::from("Public IO is wrong ")); } @@ -91,9 +107,6 @@ impl AugmentedCircui transcript.feed_scalar_num(big_u_i.unwrap().u); transcript.feed(com_t.unwrap()); let [r] = transcript.generate_challenges(); - if self.i == BaseField::one() { - println!("c: r is: {:?}", r); - } // 3.compute U_{i+1} let big_u_i1 = NIFS::::verifier(r, u_i, big_u_i.unwrap(), com_t.unwrap()); diff --git a/nova/src/ivc/ivc_verifier.rs b/nova/src/ivc/ivc_verifier.rs index 46cfc33..996773e 100644 --- a/nova/src/ivc/ivc_verifier.rs +++ b/nova/src/ivc/ivc_verifier.rs @@ -41,7 +41,7 @@ impl IVC { let folded_u_proof = zk_ivc_proof.folded_u_proof.clone().unwrap(); // 2. check that u.x = hash(i, z_0, z_i, U) - println!("z_i, {:?}", z_i); + // println!("z_i, {:?}", z_i); let hash_io = AugmentedCircuit::::hash_io(i, z_0, z_i, &big_u_i); let hash_fr = ScalarField::from_le_bytes_mod_order(&hash_io.into_bigint().to_bytes_le()); if u_i.x[0] != hash_fr { @@ -78,7 +78,7 @@ mod tests { use crate::ivc::IVCProof; use crate::nifs::nifs_verifier::gen_test_values; use super::*; - use crate::r1cs::FWitness; + use crate::r1cs::{create_trivial_pair, FInstance, FWitness}; use crate::transcript::Transcript; struct TestCircuit {} @@ -217,7 +217,7 @@ mod tests { big_u_i: trivial_instance.clone(), big_w_i: trivial_witness.clone(), }; - println!("p: u_i {:?}", u_1); + // generate W_2, U_2 and zkIVCProof via IVC proof (folded_witness, folded_instance, zk_ivc_proof) = ivc.prove( &r1cs, @@ -297,4 +297,115 @@ mod tests { // check if this state is 115. assert_eq!(ivc.augmented_circuit.z_i.state, BaseField::from(115), "Wrong state"); } + + #[test] + fn test_ivc_2() { + // This test: (x0^3 + x0 + 5) + (x1^3 + x1 + 5) + (x2^3 + x2 + 5) + (x3^3 + x2 + 5) = 130 + // x0 = 3, x1 = 4, x2 = 1, x3 = 2 + + // generate R1CS, witnesses and public input, output. + let (r1cs, witnesses, x) = gen_test_values::(vec![3, 4, 1, 2]); + let (matrix_a, _, _) = (r1cs.matrix_a.clone(), r1cs.matrix_b.clone(), r1cs.matrix_c.clone()); + + // Trusted setup + let domain_size = witnesses[0].len() + x[0].len() + 1; + let srs = Srs::new(domain_size); + let scheme = KzgScheme::new(srs); + let x_len = x[0].len(); + + // Generate witnesses and instances + let w: Vec = witnesses.iter().map(|witness| FWitness::new(witness, matrix_a.len())).collect(); + let mut u: Vec = w.iter().zip(x).map(|(w, x)| w.commit(&scheme, &x)).collect(); + + // step i + let mut i = BaseField::zero(); + + // generate trivial instance-witness pair + let (trivial_witness, trivial_instance) = create_trivial_pair(x_len, witnesses[0].len(), &scheme); + + // generate f_circuit instance + let f_circuit = TestCircuit{}; + + + // generate states + let mut z = vec![State{state: BaseField::from(0)}; 5]; + for index in 1..5 { + z[index] = f_circuit.run(&z[index - 1], &w[index-1]); + } + + let mut prover_transcript = Transcript::::default(); + let mut verifier_transcript = Transcript::::default(); + + + // create F' + let mut augmented_circuit = AugmentedCircuit::::new( + f_circuit, + &trivial_instance, + &z[0] + ); + + // generate IVC + let mut ivc = IVC:: { + scheme, + augmented_circuit + }; + + // initialize IVC proof, zkIVCProof, folded witness and folded instance + let mut ivc_proof = IVCProof::trivial_ivc_proof(&trivial_instance, &trivial_witness); + let mut zk_ivc_proof = ZkIVCProof::trivial_zk_ivc_proof(&trivial_instance); + let mut folded_witness = trivial_witness.clone(); + let mut folded_instance = trivial_instance.clone(); + + let mut res= Ok(BaseField::one()); + for step in 0..4 { + + println!("Step: {:?}", step); + + if step == 0 { + res = ivc.augmented_circuit.run( + &u[step], + None, + &w[step], + None, + ); + } else { + res = ivc.augmented_circuit.run( + &ivc_proof.u_i, + Some(&ivc_proof.big_u_i.clone()), + &ivc_proof.w_i, + Some(&zk_ivc_proof.com_t.clone().unwrap()) + ); + } + + if res.is_err() { + println!("{:?}", res); + } + assert!(res.is_ok()); + + // verifier verify this step + let verify = ivc.verify(&zk_ivc_proof, &mut verifier_transcript); + if verify.is_err() { + println!("{:?}", verify); + } + assert!(verify.is_ok()); + + // update for next step + + if step != 3 { // do not update if we have done with IVC + ivc.augmented_circuit.next_step(); + i = i + BaseField::one(); + assert_eq!(ivc.augmented_circuit.z_i.state, z[step + 1].state); + prover_transcript = Transcript::::default(); + verifier_transcript = Transcript::::default(); + + let hash_x = AugmentedCircuit::::hash_io(i, &z[0], &z[step + 1], &folded_instance); + // convert u_1_x from BaseField into ScalarField + u[step + 1].x = vec![ScalarField::from_le_bytes_mod_order(&hash_x.into_bigint().to_bytes_le())]; + + ivc_proof = IVCProof::new(&u[step + 1], &w[step + 1], &folded_instance, &folded_witness); + (folded_witness, folded_instance, zk_ivc_proof) = ivc.prove(&r1cs, &ivc_proof, &mut prover_transcript); + } + + } + } } \ No newline at end of file diff --git a/nova/src/ivc/mod.rs b/nova/src/ivc/mod.rs index c7f7665..31a106d 100644 --- a/nova/src/ivc/mod.rs +++ b/nova/src/ivc/mod.rs @@ -24,6 +24,49 @@ pub struct IVCProof { pub big_w_i: FWitness, } +impl IVCProof { + + pub fn new( + u_i: &FInstance, + w_i: &FWitness, + big_u_i: &FInstance, + big_w_i: &FWitness, + ) -> Self { + Self { + u_i: u_i.clone(), + w_i: w_i.clone(), + big_u_i: big_u_i.clone(), + big_w_i: big_w_i.clone() + } + } + + pub fn trivial_ivc_proof( + trivial_instance: &FInstance, + trivial_witness: &FWitness, + ) -> Self { + Self { + u_i: trivial_instance.clone(), + w_i: trivial_witness.clone(), + big_u_i: trivial_instance.clone(), + big_w_i: trivial_witness.clone() + } + } +} + +impl ZkIVCProof { + pub fn trivial_zk_ivc_proof( + trivial_instance: &FInstance, + ) -> Self { + Self { + u_i: trivial_instance.clone(), + big_u_i: trivial_instance.clone(), + com_t: None, + folded_u_proof: None + } + } + +} + pub struct IVC > { scheme: KzgScheme, augmented_circuit: AugmentedCircuit, diff --git a/nova/src/r1cs/mod.rs b/nova/src/r1cs/mod.rs index 812539a..5710ed1 100644 --- a/nova/src/r1cs/mod.rs +++ b/nova/src/r1cs/mod.rs @@ -67,6 +67,17 @@ impl FWitness { } } +pub fn create_trivial_pair( + x_len: usize, + w_len: usize, + scheme: &KzgScheme) +-> (FWitness, FInstance){ + let trivial_x = vec![ScalarField::from(0); x_len]; + let trivial_witness = FWitness::new_trivial_witness(w_len); + let trivial_instance = trivial_witness.commit(&scheme, &trivial_x); + (trivial_witness, trivial_instance) +} + pub fn is_satis_relaxed( r1cs: R1CS, f_instance: FInstance, From 8c6f950a217cd825c990fbc4dff2ba4257ea7ed3 Mon Sep 17 00:00:00 2001 From: vanhger Date: Wed, 15 May 2024 17:28:19 +0700 Subject: [PATCH 15/18] Refine code and add more test cases. --- nova/src/circuit.rs | 228 ++++++++++++++++++++++++++++- nova/src/ivc/ivc_prover.rs | 1 + nova/src/ivc/ivc_verifier.rs | 252 +++++++++++++++++---------------- nova/src/ivc/mod.rs | 7 +- nova/src/nifs/mod.rs | 4 +- nova/src/nifs/nifs_prover.rs | 43 ++++++ nova/src/nifs/nifs_verifier.rs | 24 ++-- nova/src/r1cs/mod.rs | 29 ++-- nova/src/transcript.rs | 3 +- nova/src/utils.rs | 10 +- 10 files changed, 446 insertions(+), 155 deletions(-) diff --git a/nova/src/circuit.rs b/nova/src/circuit.rs index 8d38676..08ab3f4 100644 --- a/nova/src/circuit.rs +++ b/nova/src/circuit.rs @@ -1,7 +1,6 @@ use std::marker::PhantomData; use std::ops::Add; -use ark_ec::CurveGroup; -use ark_ff::{BigInteger, Field, One, PrimeField, Zero}; +use ark_ff::{BigInteger, One, PrimeField, Zero}; use ark_serialize::CanonicalSerialize; use sha2::Digest; use kzg::commitment::KzgCommitment; @@ -10,7 +9,6 @@ use crate::nifs::{NIFS}; use crate::r1cs::{FInstance, FWitness}; use crate::transcript::Transcript; -pub type ConstraintF = <::BaseField as Field>::BasePrimeField; /// State structure of IVC, which is presented in BaseField of Bsn12_381 curve /// Todo: Implement a general version. @@ -39,6 +37,7 @@ pub struct AugmentedCircuit, } +#[allow(dead_code)] impl AugmentedCircuit { pub fn new( @@ -141,8 +140,10 @@ impl AugmentedCircui pub fn next_step(&mut self) { self.z_i = self.z_i1.clone().unwrap(); + self.z_i1 = None; self.i = self.i + BaseField::one(); self.hash_x = self.hash_x_next; + self.hash_x_next = None; } /// A function compute public IO of an instance: u.x = hash(i, z0, zi, Ui). @@ -170,3 +171,224 @@ impl AugmentedCircui } } +#[cfg(test)] +#[allow(dead_code)] +mod test { + use sha2::Sha256; + use kzg::scheme::KzgScheme; + use kzg::srs::Srs; + use crate::nifs::nifs_verifier::gen_test_values; + use crate::r1cs::create_trivial_pair; + use super::*; + + struct TestCircuit {} + impl FCircuit for TestCircuit { + fn run(&self, z_i: &State, w_i: &FWitness) -> State { + let x = w_i.w[0].clone(); + let res = x * x * x + x + ScalarField::from(5); + // because res is in scalar field, we need to convert it into base_field + let base_res = BaseField::from_le_bytes_mod_order(&res.into_bigint().to_bytes_le()); + + State { + state: z_i.state + base_res + } + } + } + + #[test] + fn test_augmented_circuit_01() { + // generate R1CS, witnesses and public input, output. + let (r1cs, witnesses, x) = gen_test_values::(vec![3]); + let (matrix_a, _, _) = (r1cs.matrix_a.clone(), r1cs.matrix_b.clone(), r1cs.matrix_c.clone()); + + // Trusted setup + let domain_size = witnesses[0].len() + x[0].len() + 1; + let srs = Srs::new(domain_size); + let scheme = KzgScheme::new(srs); + + let w_0 = FWitness::new(&witnesses[0], matrix_a.len()); + let u_0 = w_0.commit(&scheme, &x[0]); + + // generate trivial_instance + let (_, trivial_instance) = create_trivial_pair(x[0].len(), witnesses[0].len(), &scheme); + + // generate f_circuit instance + let f_circuit = TestCircuit{}; + + // generate state + let z_0 = State{state: BaseField::from(0)}; + let z_1 = State{state: BaseField::from(35)}; + // let prover_transcript = Transcript::::default(); + + // create F' + let mut augmented_circuit = AugmentedCircuit:: { + f_circuit, + i: BaseField::zero(), + trivial_instance: trivial_instance.clone(), + z_0: z_0.clone(), + z_i: z_0.clone(), + z_i1: None, + hash_x: None, + hash_x_next: None, + phantom_data_t: PhantomData + }; + + let res1 = augmented_circuit.run( + &u_0, + None, + &w_0, + None, + ); + + // check if F' is running + if res1.is_err() { + println!("{:?}",res1); + } + + assert!(res1.is_ok()); + let hash = res1.unwrap(); + // check if the hash output is correct + assert_eq!(hash, AugmentedCircuit::::hash_io(BaseField::one(), &z_0, &z_1, &trivial_instance)); + augmented_circuit.next_step(); + // check if the state produced is correct + assert_eq!(augmented_circuit.z_i.state, z_1.state); + } + + #[test] + fn test_augmented_circuit_02() { + // generate R1CS, witnesses and public input, output. + let (r1cs, witnesses, x) = gen_test_values::(vec![3, 4]); + let (matrix_a, _, _) = (r1cs.matrix_a.clone(), r1cs.matrix_b.clone(), r1cs.matrix_c.clone()); + + // Trusted setup + let domain_size = witnesses[0].len() + x[0].len() + 1; + let srs = Srs::new(domain_size); + let scheme = KzgScheme::new(srs); + + let w_1 = FWitness::new(&witnesses[1], matrix_a.len()); + let mut u_1 = w_1.commit(&scheme, &x[1]); + + // generate trivial_instance + let (trivial_witness, trivial_instance) = create_trivial_pair(x[0].len(), witnesses[0].len(), &scheme); + + // generate f_circuit instance + let f_circuit = TestCircuit{}; + + // generate state + let z_0 = State{state: BaseField::from(0)}; + let z_1 = State{state: BaseField::from(35)}; + let z_2 = State{state: BaseField::from(108)}; + let mut prover_transcript = Transcript::::default(); + + let u_1_x = AugmentedCircuit::::hash_io(BaseField::from(1), &z_0, &z_1, &trivial_instance); + // convert u_1_x from BaseField into ScalarField + u_1.x = vec![ScalarField::from_le_bytes_mod_order(&u_1_x.into_bigint().to_bytes_le())]; + + let (_, folded_instance, com_t, _) = NIFS::::prover(&r1cs, &w_1, &trivial_witness, &u_1, &trivial_instance, &scheme, &mut prover_transcript); + + // create F' + let mut augmented_circuit = AugmentedCircuit:: { + f_circuit, + i: BaseField::from(1), + trivial_instance: trivial_instance.clone(), + z_0: z_0.clone(), + z_i: z_1.clone(), + z_i1: None, + hash_x: Some(u_1_x), + hash_x_next: None, + phantom_data_t: PhantomData + }; + + + + let res1 = augmented_circuit.run( + &u_1, + Some(&trivial_instance), + &w_1, + Some(&com_t) + ); + + // check if F' is running + if res1.is_err() { + println!("{:?}",res1); + } + + assert!(res1.is_ok()); + let hash = res1.unwrap(); + // check if the hash output is correct + assert_eq!(hash, AugmentedCircuit::::hash_io(BaseField::from(2), &z_0, &z_2, &folded_instance)); + augmented_circuit.next_step(); + // check if the state produced is correct + assert_eq!(augmented_circuit.z_i.state, z_2.state); + } + + + #[test] + #[should_panic] + fn test_augmented_circuit_03() { + // generate R1CS, witnesses and public input, output. + let (r1cs, witnesses, x) = gen_test_values::(vec![3, 4]); + let (matrix_a, _, _) = (r1cs.matrix_a.clone(), r1cs.matrix_b.clone(), r1cs.matrix_c.clone()); + + // Trusted setup + let domain_size = witnesses[0].len() + x[0].len() + 1; + let srs = Srs::new(domain_size); + let scheme = KzgScheme::new(srs); + + let w_1 = FWitness::new(&witnesses[1], matrix_a.len()); + let mut u_1 = w_1.commit(&scheme, &x[1]); + + // generate trivial_instance + let (trivial_witness, trivial_instance) = create_trivial_pair(x[0].len(), witnesses[0].len(), &scheme); + + // generate f_circuit instance + let f_circuit = TestCircuit{}; + + // generate state + let z_0 = State{state: BaseField::from(0)}; + let z_1 = State{state: BaseField::from(35)}; + let z_2 = State{state: BaseField::from(130)}; + let mut prover_transcript = Transcript::::default(); + + let u_1_x = AugmentedCircuit::::hash_io(BaseField::from(1), &z_0, &z_1, &trivial_instance); + // convert u_1_x from BaseField into ScalarField + u_1.x = vec![ScalarField::from_le_bytes_mod_order(&u_1_x.into_bigint().to_bytes_le())]; + + let (_, folded_instance, com_t, _) = NIFS::::prover(&r1cs, &w_1, &trivial_witness, &u_1, &trivial_instance, &scheme, &mut prover_transcript); + + // create F' + let mut augmented_circuit = AugmentedCircuit:: { + f_circuit, + i: BaseField::from(1), + trivial_instance: trivial_instance.clone(), + z_0: z_0.clone(), + z_i: z_1.clone(), + z_i1: None, + hash_x: Some(u_1_x), + hash_x_next: None, + phantom_data_t: PhantomData + }; + + + + let res1 = augmented_circuit.run( + &u_1, + Some(&trivial_instance), + &w_1, + Some(&com_t) + ); + + // check if F' is running + if res1.is_err() { + println!("{:?}",res1); + } + + assert!(res1.is_ok()); + let hash = res1.unwrap(); + // check if the hash output is correct + assert_eq!(hash, AugmentedCircuit::::hash_io(BaseField::from(2), &z_0, &z_2, &folded_instance)); + augmented_circuit.next_step(); + // check if the state produced is correct + assert_eq!(augmented_circuit.z_i.state, z_2.state); + } +} diff --git a/nova/src/ivc/ivc_prover.rs b/nova/src/ivc/ivc_prover.rs index f1428c0..9214090 100644 --- a/nova/src/ivc/ivc_prover.rs +++ b/nova/src/ivc/ivc_prover.rs @@ -7,6 +7,7 @@ use crate::nifs::NIFS; use crate::r1cs::{FInstance, FWitness, R1CS}; use crate::transcript::Transcript; +#[allow(dead_code)] impl IVC { pub fn prove( &self, diff --git a/nova/src/ivc/ivc_verifier.rs b/nova/src/ivc/ivc_verifier.rs index 996773e..12c7a5f 100644 --- a/nova/src/ivc/ivc_verifier.rs +++ b/nova/src/ivc/ivc_verifier.rs @@ -1,11 +1,12 @@ use ark_ff::{BigInteger, One, PrimeField, Zero}; use sha2::Digest; use kzg::types::{BaseField, ScalarField}; -use crate::circuit::{AugmentedCircuit, FCircuit, State}; +use crate::circuit::{AugmentedCircuit, FCircuit}; use crate::ivc::{IVC, ZkIVCProof}; use crate::nifs::NIFS; use crate::transcript::Transcript; +#[allow(dead_code)] impl IVC { pub fn verify( &mut self, @@ -69,7 +70,7 @@ impl IVC { } #[cfg(test)] - +#[allow(dead_code)] mod tests { use std::marker::PhantomData; use sha2::Sha256; @@ -80,11 +81,11 @@ mod tests { use super::*; use crate::r1cs::{create_trivial_pair, FInstance, FWitness}; use crate::transcript::Transcript; - + use crate::circuit::State; struct TestCircuit {} impl FCircuit for TestCircuit { fn run(&self, z_i: &State, w_i: &FWitness) -> State { - let x = w_i.w[1].clone(); + let x = w_i.w[0].clone(); let res = x * x * x + x + ScalarField::from(5); // because res is in scalar field, we need to convert it into base_field let base_res = BaseField::from_le_bytes_mod_order(&res.into_bigint().to_bytes_le()); @@ -96,7 +97,118 @@ mod tests { } #[test] - fn test_ivc_step_by_step_1() { + fn test_ivc() { + // This test: (x0^3 + x0 + 5) + (x1^3 + x1 + 5) + (x2^3 + x2 + 5) + (x3^3 + x2 + 5) = 130 + // x0 = 3, x1 = 4, x2 = 1, x3 = 2 + + // generate R1CS, witnesses and public input, output. + let (r1cs, witnesses, x) = gen_test_values::(vec![3, 4, 1, 2]); + let (matrix_a, _, _) = (r1cs.matrix_a.clone(), r1cs.matrix_b.clone(), r1cs.matrix_c.clone()); + + // Trusted setup + let domain_size = witnesses[0].len() + x[0].len() + 1; + let srs = Srs::new(domain_size); + let scheme = KzgScheme::new(srs); + let x_len = x[0].len(); + + // Generate witnesses and instances + let w: Vec = witnesses.iter().map(|witness| FWitness::new(witness, matrix_a.len())).collect(); + let mut u: Vec = w.iter().zip(x).map(|(w, x)| w.commit(&scheme, &x)).collect(); + + // step i + let mut i = BaseField::zero(); + + // generate trivial instance-witness pair + let (trivial_witness, trivial_instance) = create_trivial_pair(x_len, witnesses[0].len(), &scheme); + + // generate f_circuit instance + let f_circuit = TestCircuit{}; + + // generate states + let mut z = vec![State{state: BaseField::from(0)}; 5]; + for index in 1..5 { + z[index] = f_circuit.run(&z[index - 1], &w[index-1]); + } + + let mut prover_transcript; + let mut verifier_transcript = Transcript::::default(); + + + // create F' + let augmented_circuit = AugmentedCircuit::::new( + f_circuit, + &trivial_instance, + &z[0] + ); + + // generate IVC + let mut ivc = IVC:: { + scheme, + augmented_circuit + }; + + // initialize IVC proof, zkIVCProof, folded witness and folded instance + let mut ivc_proof = IVCProof::trivial_ivc_proof(&trivial_instance, &trivial_witness); + let mut zk_ivc_proof = ZkIVCProof::trivial_zk_ivc_proof(&trivial_instance); + let mut folded_witness = trivial_witness.clone(); + let mut folded_instance = trivial_instance.clone(); + + let mut res; + for step in 0..4 { + + println!("Step: {:?}", step); + + if step == 0 { + res = ivc.augmented_circuit.run( + &u[step], + None, + &w[step], + None, + ); + } else { + res = ivc.augmented_circuit.run( + &ivc_proof.u_i, + Some(&ivc_proof.big_u_i.clone()), + &ivc_proof.w_i, + Some(&zk_ivc_proof.com_t.clone().unwrap()) + ); + } + + if res.is_err() { + println!("{:?}", res); + } + assert!(res.is_ok()); + + // verifier verify this step + let verify = ivc.verify(&zk_ivc_proof, &mut verifier_transcript); + if verify.is_err() { + println!("{:?}", verify); + } + assert!(verify.is_ok()); + + // update for next step + + if step != 3 { // do not update if we have done with IVC + ivc.augmented_circuit.next_step(); + i = i + BaseField::one(); + assert_eq!(ivc.augmented_circuit.z_i.state, z[step + 1].state); + prover_transcript = Transcript::::default(); + verifier_transcript = Transcript::::default(); + + let hash_x = AugmentedCircuit::::hash_io(i, &z[0], &z[step + 1], &folded_instance); + // convert u_1_x from BaseField into ScalarField + u[step + 1].x = vec![ScalarField::from_le_bytes_mod_order(&hash_x.into_bigint().to_bytes_le())]; + + // generate ivc_proof and zkSNARK proof. + ivc_proof = IVCProof::new(&u[step + 1], &w[step + 1], &folded_instance, &folded_witness); + (folded_witness, folded_instance, zk_ivc_proof) = ivc.prove(&r1cs, &ivc_proof, &mut prover_transcript); + } + } + } + + #[test] + #[allow(dead_code)] + fn test_ivc_step_by_step() { // This test: (x0^3 + x0 + 5) + (x1^3 + x1 + 5) + (x2^3 + x2 + 5)= 115 // x0 = 3, x1 = 4, x2 = 1 @@ -133,11 +245,11 @@ mod tests { let z_1 = f_circuit.run(&z_0, &w_0); let z_2 = f_circuit.run(&z_1, &w_1); - let mut prover_transcript = Transcript::::default(); + let mut prover_transcript; let mut verifier_transcript = Transcript::::default(); // create F' - let mut augmented_circuit = AugmentedCircuit:: { + let augmented_circuit = AugmentedCircuit:: { f_circuit, i: BaseField::zero(), trivial_instance: trivial_instance.clone(), @@ -155,7 +267,7 @@ mod tests { augmented_circuit }; - // initialize IVC proof, zkIVCProof, folded witness and folded instance + // initialize IVC proof, zkIVCProof, folded witness (W) and folded instance (U) let mut ivc_proof = IVCProof { w_i: trivial_witness.clone(), u_i: trivial_instance.clone(), @@ -164,14 +276,15 @@ mod tests { }; let mut zk_ivc_proof = ZkIVCProof { - u_i: trivial_instance.clone(), - big_u_i: trivial_instance.clone(), + u_i: ivc_proof.u_i, + big_u_i: ivc_proof.big_u_i, com_t: None, folded_u_proof: None, }; - let mut folded_witness = trivial_witness.clone(); - let mut folded_instance = trivial_instance.clone(); + + let folded_witness; + let folded_instance; println!("Step 1"); // run F' for the first time @@ -264,7 +377,8 @@ mod tests { big_w_i: folded_witness // W_2 }; - (folded_witness, folded_instance, zk_ivc_proof) = ivc.prove( + // Compute W_3, U_3, and zkSNARK proof + (_, _, zk_ivc_proof) = ivc.prove( &r1cs, &ivc_proof, &mut prover_transcript @@ -293,119 +407,7 @@ mod tests { // update next step ivc.augmented_circuit.next_step(); - i = i + BaseField::one(); // check if this state is 115. assert_eq!(ivc.augmented_circuit.z_i.state, BaseField::from(115), "Wrong state"); } - - #[test] - fn test_ivc_2() { - // This test: (x0^3 + x0 + 5) + (x1^3 + x1 + 5) + (x2^3 + x2 + 5) + (x3^3 + x2 + 5) = 130 - // x0 = 3, x1 = 4, x2 = 1, x3 = 2 - - // generate R1CS, witnesses and public input, output. - let (r1cs, witnesses, x) = gen_test_values::(vec![3, 4, 1, 2]); - let (matrix_a, _, _) = (r1cs.matrix_a.clone(), r1cs.matrix_b.clone(), r1cs.matrix_c.clone()); - - // Trusted setup - let domain_size = witnesses[0].len() + x[0].len() + 1; - let srs = Srs::new(domain_size); - let scheme = KzgScheme::new(srs); - let x_len = x[0].len(); - - // Generate witnesses and instances - let w: Vec = witnesses.iter().map(|witness| FWitness::new(witness, matrix_a.len())).collect(); - let mut u: Vec = w.iter().zip(x).map(|(w, x)| w.commit(&scheme, &x)).collect(); - - // step i - let mut i = BaseField::zero(); - - // generate trivial instance-witness pair - let (trivial_witness, trivial_instance) = create_trivial_pair(x_len, witnesses[0].len(), &scheme); - - // generate f_circuit instance - let f_circuit = TestCircuit{}; - - - // generate states - let mut z = vec![State{state: BaseField::from(0)}; 5]; - for index in 1..5 { - z[index] = f_circuit.run(&z[index - 1], &w[index-1]); - } - - let mut prover_transcript = Transcript::::default(); - let mut verifier_transcript = Transcript::::default(); - - - // create F' - let mut augmented_circuit = AugmentedCircuit::::new( - f_circuit, - &trivial_instance, - &z[0] - ); - - // generate IVC - let mut ivc = IVC:: { - scheme, - augmented_circuit - }; - - // initialize IVC proof, zkIVCProof, folded witness and folded instance - let mut ivc_proof = IVCProof::trivial_ivc_proof(&trivial_instance, &trivial_witness); - let mut zk_ivc_proof = ZkIVCProof::trivial_zk_ivc_proof(&trivial_instance); - let mut folded_witness = trivial_witness.clone(); - let mut folded_instance = trivial_instance.clone(); - - let mut res= Ok(BaseField::one()); - for step in 0..4 { - - println!("Step: {:?}", step); - - if step == 0 { - res = ivc.augmented_circuit.run( - &u[step], - None, - &w[step], - None, - ); - } else { - res = ivc.augmented_circuit.run( - &ivc_proof.u_i, - Some(&ivc_proof.big_u_i.clone()), - &ivc_proof.w_i, - Some(&zk_ivc_proof.com_t.clone().unwrap()) - ); - } - - if res.is_err() { - println!("{:?}", res); - } - assert!(res.is_ok()); - - // verifier verify this step - let verify = ivc.verify(&zk_ivc_proof, &mut verifier_transcript); - if verify.is_err() { - println!("{:?}", verify); - } - assert!(verify.is_ok()); - - // update for next step - - if step != 3 { // do not update if we have done with IVC - ivc.augmented_circuit.next_step(); - i = i + BaseField::one(); - assert_eq!(ivc.augmented_circuit.z_i.state, z[step + 1].state); - prover_transcript = Transcript::::default(); - verifier_transcript = Transcript::::default(); - - let hash_x = AugmentedCircuit::::hash_io(i, &z[0], &z[step + 1], &folded_instance); - // convert u_1_x from BaseField into ScalarField - u[step + 1].x = vec![ScalarField::from_le_bytes_mod_order(&hash_x.into_bigint().to_bytes_le())]; - - ivc_proof = IVCProof::new(&u[step + 1], &w[step + 1], &folded_instance, &folded_witness); - (folded_witness, folded_instance, zk_ivc_proof) = ivc.prove(&r1cs, &ivc_proof, &mut prover_transcript); - } - - } - } } \ No newline at end of file diff --git a/nova/src/ivc/mod.rs b/nova/src/ivc/mod.rs index 31a106d..2280af5 100644 --- a/nova/src/ivc/mod.rs +++ b/nova/src/ivc/mod.rs @@ -1,7 +1,6 @@ mod ivc_prover; mod ivc_verifier; -use ark_ff::PrimeField; use sha2::Digest; use kzg::commitment::KzgCommitment; use kzg::scheme::KzgScheme; @@ -24,6 +23,7 @@ pub struct IVCProof { pub big_w_i: FWitness, } +#[allow(dead_code)] impl IVCProof { pub fn new( @@ -53,6 +53,7 @@ impl IVCProof { } } +#[allow(dead_code)] impl ZkIVCProof { pub fn trivial_zk_ivc_proof( trivial_instance: &FInstance, @@ -68,7 +69,7 @@ impl ZkIVCProof { } pub struct IVC > { - scheme: KzgScheme, - augmented_circuit: AugmentedCircuit, + pub(crate) scheme: KzgScheme, + pub(crate) augmented_circuit: AugmentedCircuit, } diff --git a/nova/src/nifs/mod.rs b/nova/src/nifs/mod.rs index 85fa54b..017045a 100644 --- a/nova/src/nifs/mod.rs +++ b/nova/src/nifs/mod.rs @@ -85,8 +85,8 @@ impl NIFS { let new_com_e = KzgCommitment((fi1.com_e.0 + com_t.0.mul(r) + fi2.com_e.0.mul(r * r)).into_affine()); let new_com_w = KzgCommitment((fi1.com_w.0 + fi2.com_w.0.mul(r)).into_affine()); - let new_u = fi1.u + fi1.u * r; - let new_x = fi1.x.iter().zip(&fi1.x).map(|(a, b)| { + let new_u = fi1.u + fi2.u * r; + let new_x = fi1.x.iter().zip(&fi2.x).map(|(a, b)| { *a + *b * r }).collect(); diff --git a/nova/src/nifs/nifs_prover.rs b/nova/src/nifs/nifs_prover.rs index d366a56..2af1a18 100644 --- a/nova/src/nifs/nifs_prover.rs +++ b/nova/src/nifs/nifs_prover.rs @@ -64,3 +64,46 @@ impl NIFS { } } } + +#[cfg(test)] + +mod test { + use sha2::Sha256; + use kzg::srs::Srs; + use crate::nifs::nifs_verifier::gen_test_values; + use crate::r1cs::is_r1cs_satisfied; + use super::*; + + #[test] + pub fn test_prover_folding() { + // generate R1CS, witnesses and public input, output. + let (r1cs, witnesses, x) = gen_test_values::(vec![3, 4]); + let (matrix_a, _, _) = (r1cs.matrix_a.clone(), r1cs.matrix_b.clone(), r1cs.matrix_c.clone()); + + // Trusted setup + let domain_size = witnesses[0].len() + x[0].len() + 1; + let srs = Srs::new(domain_size); + let scheme = KzgScheme::new(srs); + + // Generate witnesses and instances + let w: Vec = witnesses.iter().map(|witness| FWitness::new(witness, matrix_a.len())).collect(); + let u: Vec = w.iter().zip(x).map(|(w, x)| w.commit(&scheme, &x)).collect(); + + let mut transcript = Transcript::::default(); + + let (folded_witness, folded_instance, _, _) = NIFS::::prover( + &r1cs, + &w[0], &w[1], + &u[0], &u[1], + &scheme, + &mut transcript + ); + + let ok = is_r1cs_satisfied(&r1cs,&folded_instance, &folded_witness, &scheme); + + if ok.is_err() { + println!("{:?}", ok); + } + assert!(ok.is_ok()); + } +} diff --git a/nova/src/nifs/nifs_verifier.rs b/nova/src/nifs/nifs_verifier.rs index c8525f1..d58db5a 100644 --- a/nova/src/nifs/nifs_verifier.rs +++ b/nova/src/nifs/nifs_verifier.rs @@ -92,27 +92,28 @@ impl NIFS { } } +#[allow(dead_code)] pub fn gen_test_values(inputs: Vec) -> (R1CS, Vec>, Vec>) { // R1CS for: x^3 + x + 5 = y (example from article // https://vitalik.eth.limo/general/2016/12/10/qap.html ) let a = to_f_matrix::(vec![ + vec![1, 0, 0, 0, 0, 0], vec![0, 1, 0, 0, 0, 0], - vec![0, 0, 0, 1, 0, 0], - vec![0, 1, 0, 0, 1, 0], - vec![5, 0, 0, 0, 0, 1], + vec![1, 0, 1, 0, 0, 0], + vec![0, 0, 0, 1, 0, 5], ]); let b = to_f_matrix::(vec![ - vec![0, 1, 0, 0, 0, 0], - vec![0, 1, 0, 0, 0, 0], vec![1, 0, 0, 0, 0, 0], vec![1, 0, 0, 0, 0, 0], + vec![0, 0, 0, 0, 0, 1], + vec![0, 0, 0, 0, 0, 1], ]); let c = to_f_matrix::(vec![ + vec![0, 1, 0, 0, 0, 0], + vec![0, 0, 1, 0, 0, 0], vec![0, 0, 0, 1, 0, 0], vec![0, 0, 0, 0, 1, 0], - vec![0, 0, 0, 0, 0, 1], - vec![0, 0, 1, 0, 0, 0], ]); // generate n witnesses @@ -120,26 +121,24 @@ pub fn gen_test_values(inputs: Vec) -> (R1CS, Vec> = Vec::new(); for input in inputs { let w_i = to_f_vec::(vec![ - 1, input, - input * input * input + input + 5, // x^3 + x + 5 input * input, // x^2 input * input * input, // x^2 * x input * input * input + input, // x^3 + x ]); w.push(w_i.clone()); - let x_i = to_f_vec::(vec![input * input * input + input + 5]); + let x_i = to_f_vec::(vec![input * input * input + input + 5]); // output: x^3 + x + 5 x.push(x_i.clone()); } // println!("w: {:?}", w); // println!("x: {:?}", x); - let r1cs = R1CS:: { matrix_a: a, matrix_b: b, matrix_c: c, num_io: 1, num_vars: 6 }; + let r1cs = R1CS:: { matrix_a: a, matrix_b: b, matrix_c: c, num_io: 1, num_vars: 4 }; (r1cs, w, x) } #[cfg(test)] - +#[allow(dead_code)] mod tests { use super::*; use sha2::Sha256; @@ -160,6 +159,7 @@ mod tests { let mut prover_transcript = Transcript::::default(); let mut verifier_transcript = Transcript::::default(); + // generate witnesses and instances let fw1 = FWitness::new(&witnesses[0], matrix_a.len()); let fw2 = FWitness::new(&witnesses[1], matrix_a.len()); diff --git a/nova/src/r1cs/mod.rs b/nova/src/r1cs/mod.rs index 5710ed1..6d17f09 100644 --- a/nova/src/r1cs/mod.rs +++ b/nova/src/r1cs/mod.rs @@ -34,7 +34,7 @@ pub struct FWitness { pub w: Vec, // pub rW: ScalarField, } - +#[allow(dead_code)] impl FWitness { pub fn new(w: &Vec, len: usize) -> Self { FWitness { @@ -67,6 +67,7 @@ impl FWitness { } } +#[allow(dead_code)] pub fn create_trivial_pair( x_len: usize, w_len: usize, @@ -78,11 +79,12 @@ pub fn create_trivial_pair( (trivial_witness, trivial_instance) } -pub fn is_satis_relaxed( - r1cs: R1CS, - f_instance: FInstance, - f_witness: FWitness, - scheme: KzgScheme +#[allow(dead_code)] +pub fn is_r1cs_satisfied( + r1cs: &R1CS, + f_instance: &FInstance, + f_witness: &FWitness, + scheme: &KzgScheme ) -> Result<(), String> { if r1cs.num_vars != f_witness.w.len() { return Err(String::from("Witness does not match with matrices")); @@ -125,10 +127,12 @@ mod tests { use kzg::srs::Srs; use kzg::types::ScalarField; use crate::nifs::nifs_verifier::gen_test_values; + use crate::r1cs::{FInstance, FWitness, is_r1cs_satisfied}; #[test] - pub fn test_r1cs() { - let (r1cs, witnesses, x) = gen_test_values::(vec![3, 4]); + pub fn test_r1cs_satisfaction_condition() { + // generate R1CS, witnesses and public input, output. + let (r1cs, witnesses, x) = gen_test_values::(vec![3]); let (matrix_a, _, _) = (r1cs.matrix_a.clone(), r1cs.matrix_b.clone(), r1cs.matrix_c.clone()); // Trusted setup @@ -136,6 +140,15 @@ mod tests { let srs = Srs::new(domain_size); let scheme = KzgScheme::new(srs); + // Generate witnesses and instances + let w: Vec = witnesses.iter().map(|witness| FWitness::new(witness, matrix_a.len())).collect(); + let u: Vec = w.iter().zip(x).map(|(w, x)| w.commit(&scheme, &x)).collect(); + + let ok = is_r1cs_satisfied(&r1cs,&u[0], &w[0], &scheme); + if ok.is_err() { + println!("{:?}", ok); + } + assert!(ok.is_ok()); } } \ No newline at end of file diff --git a/nova/src/transcript.rs b/nova/src/transcript.rs index 7654315..255e9c2 100644 --- a/nova/src/transcript.rs +++ b/nova/src/transcript.rs @@ -1,7 +1,7 @@ use std::marker::PhantomData; use ark_bls12_381::Fr; -use ark_ff::{PrimeField, UniformRand}; +use ark_ff::{UniformRand}; use ark_serialize::{CanonicalSerialize, Write}; use rand::rngs::StdRng; use rand::SeedableRng; @@ -23,6 +23,7 @@ pub struct Transcript { _phantom_data_t: PhantomData, } +#[allow(dead_code)] impl Transcript { /// Creates a new `Transcript` instance from a list of commitments. /// diff --git a/nova/src/utils.rs b/nova/src/utils.rs index df15373..762247c 100644 --- a/nova/src/utils.rs +++ b/nova/src/utils.rs @@ -1,5 +1,5 @@ use ark_ff::PrimeField; - +#[allow(dead_code)] pub fn matrix_vector_product(matrix: &Vec>, z: &Vec) -> Vec { let mut r: Vec = vec![F::zero(); matrix.len()]; for i in 0..matrix.len() { @@ -9,6 +9,8 @@ pub fn matrix_vector_product(matrix: &Vec>, z: &Vec) -> } r } + +#[allow(dead_code)] pub fn hadamard_product(a: &Vec, b: &Vec) -> Vec { let mut r: Vec = vec![F::zero(); a.len()]; @@ -18,6 +20,7 @@ pub fn hadamard_product(a: &Vec, b: &Vec) -> Vec { r } +#[allow(dead_code)] pub fn vector_elem_product(a: &Vec, u: F) -> Vec { let mut r: Vec = vec![F::zero(); a.len()]; for i in 0..a.len() { @@ -26,6 +29,7 @@ pub fn vector_elem_product(a: &Vec, u: F) -> Vec { r } +#[allow(dead_code)] pub fn vec_sub(a: &Vec, b: &Vec) -> Vec { assert_eq!(a.len(), b.len()); let mut r: Vec = vec![F::zero(); a.len()]; @@ -35,6 +39,7 @@ pub fn vec_sub(a: &Vec, b: &Vec) -> Vec { r } +#[allow(dead_code)] pub fn vec_add(a: &Vec, b: &Vec) -> Vec { assert_eq!(a.len(), b.len()); let mut r: Vec = vec![F::zero(); a.len()]; @@ -44,6 +49,7 @@ pub fn vec_add(a: &Vec, b: &Vec) -> Vec { r } +#[allow(dead_code)] pub fn vec_equal(a: &Vec, b: &Vec) -> bool { if a.len() != b.len() { return false; @@ -57,6 +63,7 @@ pub fn vec_equal(a: &Vec, b: &Vec) -> bool { true } +#[allow(dead_code)] pub fn to_f_matrix (matrix: Vec>) -> Vec> { let mut r: Vec> = vec![Vec::new(); matrix.len()]; for i in 0..matrix.len() { @@ -68,6 +75,7 @@ pub fn to_f_matrix (matrix: Vec>) -> Vec> { r } +#[allow(dead_code)] pub fn to_f_vec(z: Vec) -> Vec { let mut r: Vec = vec![F::zero(); z.len()]; for i in 0..z.len() { From a98382cdc18126957ef4ccc234ce0342227cb3bc Mon Sep 17 00:00:00 2001 From: vanhger Date: Wed, 15 May 2024 22:17:53 +0700 Subject: [PATCH 16/18] refactor: Refine code and add comments --- nova/src/circuit.rs | 60 ++++++++++++++----------- nova/src/ivc/ivc_prover.rs | 7 ++- nova/src/ivc/ivc_verifier.rs | 8 ++-- nova/src/ivc/mod.rs | 7 ++- nova/src/nifs/mod.rs | 12 +++++ nova/src/nifs/nifs_prover.rs | 8 +++- nova/src/nifs/nifs_verifier.rs | 7 +++ nova/src/r1cs/mod.rs | 9 +++- nova/src/transcript.rs | 9 ++++ nova/src/utils.rs | 81 +++++++++++++++++++++++++++++++++- 10 files changed, 173 insertions(+), 35 deletions(-) diff --git a/nova/src/circuit.rs b/nova/src/circuit.rs index 08ab3f4..7028b2f 100644 --- a/nova/src/circuit.rs +++ b/nova/src/circuit.rs @@ -26,14 +26,22 @@ pub trait FCircuit { /// F' circuit pub struct AugmentedCircuit { + // F function pub f_circuit: FC, + // i is the step of IVC pub i: BaseField, - pub trivial_instance: FInstance, // trivial instance. + // trivial instance u⊥ + pub trivial_instance: FInstance, + // The initial state z_0 pub z_0: State, - pub z_i1: Option, // store the next state, use for update step. + // The current state pub z_i: State, - pub hash_x: Option, - pub hash_x_next: Option, // store the next hash IO, use for update step + // The next state z_{i+1} = F(z_i, w_i). + pub z_i1: Option, + // h_i = hash(i, z0, zi, Ui) + pub h_i: Option, + // store the next hash IO: h_{i+1} = hash(i + 1, z0, z{i+1}, U{i+1}) + pub h_i1: Option, pub phantom_data_t: PhantomData, } @@ -52,8 +60,8 @@ impl AugmentedCircui z_0: z_0.clone(), z_i: z_0.clone(), z_i1: None, - hash_x: None, - hash_x_next: None, + h_i: None, + h_i1: None, phantom_data_t: PhantomData } } @@ -73,12 +81,12 @@ impl AugmentedCircui } // check that if i > 0 then the hash_x must exist - if self.hash_x.is_none() { + if self.h_i.is_none() { return Err(String::from("The hash public IO must exist")) } // get hash_x - let hash_x = self.hash_x.clone().unwrap(); + let hash_x = self.h_i.clone().unwrap(); // 1. check that u.x =? hash_x // Because u_i.x is in ScalarField while hash_x is in BaseField, they need to @@ -117,7 +125,7 @@ impl AugmentedCircui let new_hash= Self::hash_io(self.i.add(BaseField::one()), &self.z_0, &z_i1, &big_u_i1); // store the next hash - self.hash_x_next = Some(new_hash); + self.h_i1 = Some(new_hash); // store the next state self.z_i1 = Some(z_i1); @@ -130,39 +138,41 @@ impl AugmentedCircui let new_hash = Self::hash_io(BaseField::one(), &self.z_0, &z_i1, &self.trivial_instance); // store the next hash - self.hash_x_next = Some(new_hash); + self.h_i1 = Some(new_hash); // store the next state self.z_i1 = Some(z_i1); } - return Ok(self.hash_x_next.unwrap()); + // 4. output the hash + return Ok(self.h_i1.unwrap()); } + /// updating F' function for the next step of IVC. pub fn next_step(&mut self) { self.z_i = self.z_i1.clone().unwrap(); self.z_i1 = None; self.i = self.i + BaseField::one(); - self.hash_x = self.hash_x_next; - self.hash_x_next = None; + self.h_i = self.h_i1; + self.h_i1 = None; } - /// A function compute public IO of an instance: u.x = hash(i, z0, zi, Ui). + /// A function computes public IO of an instance: u.x = hash(i, z0, zi, Ui). pub fn hash_io( i: BaseField, z_0: &State, z_i: &State, - big_u: &FInstance + big_u_i: &FInstance ) -> BaseField { let mut hasher = T::default(); i.serialize_uncompressed(&mut hasher).unwrap(); z_0.state.serialize_uncompressed(&mut hasher).unwrap(); z_i.state.serialize_uncompressed(&mut hasher).unwrap(); - big_u.com_e.0.serialize_uncompressed(&mut hasher).unwrap(); - big_u.u.serialize_uncompressed(&mut hasher).unwrap(); - big_u.com_w.0.serialize_uncompressed(&mut hasher).unwrap(); + big_u_i.com_e.0.serialize_uncompressed(&mut hasher).unwrap(); + big_u_i.u.serialize_uncompressed(&mut hasher).unwrap(); + big_u_i.com_w.0.serialize_uncompressed(&mut hasher).unwrap(); - for x in &big_u.x { + for x in &big_u_i.x { x.serialize_uncompressed(&mut hasher).unwrap(); } @@ -228,8 +238,8 @@ mod test { z_0: z_0.clone(), z_i: z_0.clone(), z_i1: None, - hash_x: None, - hash_x_next: None, + h_i: None, + h_i1: None, phantom_data_t: PhantomData }; @@ -294,8 +304,8 @@ mod test { z_0: z_0.clone(), z_i: z_1.clone(), z_i1: None, - hash_x: Some(u_1_x), - hash_x_next: None, + h_i: Some(u_1_x), + h_i1: None, phantom_data_t: PhantomData }; @@ -364,8 +374,8 @@ mod test { z_0: z_0.clone(), z_i: z_1.clone(), z_i1: None, - hash_x: Some(u_1_x), - hash_x_next: None, + h_i: Some(u_1_x), + h_i1: None, phantom_data_t: PhantomData }; diff --git a/nova/src/ivc/ivc_prover.rs b/nova/src/ivc/ivc_prover.rs index 9214090..08dea2d 100644 --- a/nova/src/ivc/ivc_prover.rs +++ b/nova/src/ivc/ivc_prover.rs @@ -9,6 +9,9 @@ use crate::transcript::Transcript; #[allow(dead_code)] impl IVC { + + /// IVC prover will fold 2 instance-witness pairs into one via NIFS + /// and generate zkSNARK proof for it. pub fn prove( &self, r1cs: &R1CS, @@ -18,6 +21,8 @@ impl IVC { let i = self.augmented_circuit.i; if ! i.is_zero() { + + // 1 + 2. Parse Π and compute U', W' and com_T let (big_w_out, big_u_out, com_t, r) = NIFS::::prover( &r1cs, &ivc_proof.w_i, @@ -28,6 +33,7 @@ impl IVC { prover_transcript, ); + // 3. Generate zkSNARK proof let nifs_proof = NIFS::::prove(r, &big_w_out, &big_u_out, &self.scheme, prover_transcript); ( @@ -50,7 +56,6 @@ impl IVC { com_t: None, folded_u_proof: None }) - } } } \ No newline at end of file diff --git a/nova/src/ivc/ivc_verifier.rs b/nova/src/ivc/ivc_verifier.rs index 12c7a5f..f24cea0 100644 --- a/nova/src/ivc/ivc_verifier.rs +++ b/nova/src/ivc/ivc_verifier.rs @@ -8,6 +8,9 @@ use crate::transcript::Transcript; #[allow(dead_code)] impl IVC { + + /// IVC verifier will do 5 steps as mentioned in constructor 4 + /// of Nova paper. pub fn verify( &mut self, zk_ivc_proof: &ZkIVCProof, @@ -42,7 +45,6 @@ impl IVC { let folded_u_proof = zk_ivc_proof.folded_u_proof.clone().unwrap(); // 2. check that u.x = hash(i, z_0, z_i, U) - // println!("z_i, {:?}", z_i); let hash_io = AugmentedCircuit::::hash_io(i, z_0, z_i, &big_u_i); let hash_fr = ScalarField::from_le_bytes_mod_order(&hash_io.into_bigint().to_bytes_le()); if u_i.x[0] != hash_fr { @@ -256,8 +258,8 @@ mod tests { z_0: z_0.clone(), z_i: z_0.clone(), z_i1: None, - hash_x: None, - hash_x_next: None, + h_i: None, + h_i1: None, phantom_data_t: PhantomData }; diff --git a/nova/src/ivc/mod.rs b/nova/src/ivc/mod.rs index 2280af5..0fe27fa 100644 --- a/nova/src/ivc/mod.rs +++ b/nova/src/ivc/mod.rs @@ -8,7 +8,9 @@ use crate::circuit::{AugmentedCircuit, FCircuit}; use crate::nifs::{NIFSProof}; use crate::r1cs::{FInstance, FWitness}; -/// zero knowledge proof for IVC +/// This struct is the zero knowledge proof for IVC +/// π = (U, u, com_T , π_U') where U' is the folded instance +/// of u and U. pub struct ZkIVCProof { pub u_i: FInstance, pub big_u_i: FInstance, @@ -16,6 +18,7 @@ pub struct ZkIVCProof { pub folded_u_proof: Option, } +/// This struct is the proof for IVC: Π = (u, w) (U, W) pub struct IVCProof { pub u_i: FInstance, pub w_i: FWitness, @@ -40,6 +43,7 @@ impl IVCProof { } } + // Generate a trivial IVC proof. pub fn trivial_ivc_proof( trivial_instance: &FInstance, trivial_witness: &FWitness, @@ -68,6 +72,7 @@ impl ZkIVCProof { } +/// IVC structure includes a scheme for commitment and an augmented F' function pub struct IVC > { pub(crate) scheme: KzgScheme, pub(crate) augmented_circuit: AugmentedCircuit, diff --git a/nova/src/nifs/mod.rs b/nova/src/nifs/mod.rs index 017045a..8ad05f7 100644 --- a/nova/src/nifs/mod.rs +++ b/nova/src/nifs/mod.rs @@ -12,6 +12,9 @@ use crate::utils::{hadamard_product, matrix_vector_product, vec_add, vec_sub, ve pub(crate) mod nifs_verifier; mod nifs_prover; + +/// NIFS Proof is a zk proof. To convince the verifier, prover creates an opening +/// for each E and W. #[derive(Clone)] pub struct NIFSProof { pub r: ScalarField, @@ -27,6 +30,7 @@ pub struct NIFS { impl NIFS { /// Compute the cross-term T + /// T = AZ1 ◦ BZ2 + AZ2 ◦ BZ1 − u1 · CZ2 − u2 · CZ1. pub fn compute_t( r1cs: &R1CS, u1: ScalarField, @@ -54,6 +58,9 @@ impl NIFS { t } + /// Fold two witnesses into one. + /// E ← E1 + r · T + r^2 · E2 + /// W ← W1 + r · W2 pub fn fold_witness( r: ScalarField, fw1: &FWitness, @@ -76,6 +83,11 @@ impl NIFS { } } + /// Fold two instances into one. + /// com_E ← com_E1 + r · com_T + r^2· com_E2 + /// u ← u1 + r · u2 + /// com_W ← com_W1 + r · com_W2 + /// x ← x1 + r · x2 pub fn fold_instance( r: ScalarField, fi1: &FInstance, diff --git a/nova/src/nifs/nifs_prover.rs b/nova/src/nifs/nifs_prover.rs index 2af1a18..e70c597 100644 --- a/nova/src/nifs/nifs_prover.rs +++ b/nova/src/nifs/nifs_prover.rs @@ -9,6 +9,7 @@ use crate::transcript::Transcript; impl NIFS { + /// Prover output a folded instance-witness pair, com_T and challenge r via Fiat-Shamir pub fn prover( r1cs: &R1CS, fw1: &FWitness, @@ -18,6 +19,8 @@ impl NIFS { scheme: &KzgScheme, transcript: &mut Transcript ) -> (FWitness, FInstance, KzgCommitment, ScalarField) { + + // generate Z = (W, x, u) let mut z1 = fw1.w.clone(); z1.append(&mut fi1.x.clone()); z1.push(fi1.u); @@ -40,6 +43,7 @@ impl NIFS { (new_witness, new_instance, com_t, r) } + /// Generate NIFS proof. Create openings by using KZG commitment pub fn prove( r: ScalarField, fw: &FWitness, @@ -59,8 +63,8 @@ impl NIFS { NIFSProof { r, opening_point, - opening_e: opening_e, - opening_w: opening_w + opening_e, + opening_w } } } diff --git a/nova/src/nifs/nifs_verifier.rs b/nova/src/nifs/nifs_verifier.rs index d58db5a..9646fe1 100644 --- a/nova/src/nifs/nifs_verifier.rs +++ b/nova/src/nifs/nifs_verifier.rs @@ -10,6 +10,7 @@ use crate::utils::{to_f_matrix, to_f_vec}; impl NIFS { + /// NIFS.V generate the folded instance. pub fn verifier( r: ScalarField, fi1: &FInstance, @@ -19,6 +20,8 @@ impl NIFS { NIFS::::fold_instance(r, fi1, fi2, com_t) } + /// NIFS.V can verify whether the Prover folding process was done + /// correctly or not via the NIFS proof. pub fn verify( proof: &NIFSProof, fi1: &FInstance, @@ -44,6 +47,7 @@ impl NIFS { Ok(()) } + /// Verify challenge r via Fiat-Shamir pub fn verify_challenge( r: ScalarField, fi1_u: ScalarField, @@ -65,6 +69,8 @@ impl NIFS { Ok(()) } + + /// Verify KZG opening pub fn verify_opening( proof: &NIFSProof, fi3: &FInstance, // folded instance. @@ -93,6 +99,7 @@ impl NIFS { } #[allow(dead_code)] +/// This function is only used for generate test values such as: r1cs matrices, W, x. pub fn gen_test_values(inputs: Vec) -> (R1CS, Vec>, Vec>) { // R1CS for: x^3 + x + 5 = y (example from article // https://vitalik.eth.limo/general/2016/12/10/qap.html ) diff --git a/nova/src/r1cs/mod.rs b/nova/src/r1cs/mod.rs index 6d17f09..3964b8d 100644 --- a/nova/src/r1cs/mod.rs +++ b/nova/src/r1cs/mod.rs @@ -45,6 +45,7 @@ impl FWitness { } } + /// Create a trivial witness, where E, W, and x are appropriately-sized zero vectors. pub fn new_trivial_witness(len: usize) -> Self { FWitness { e: vec![ScalarField::zero(); len], @@ -52,6 +53,7 @@ impl FWitness { } } + /// Commit a witness into its corresponding instance. pub fn commit(&self, scheme: &KzgScheme, x: &Vec) -> FInstance { let com_e = scheme.commit_vector(&self.e); // cE.0 = cE.0.mul(self.rE).into_affine(); @@ -67,7 +69,9 @@ impl FWitness { } } -#[allow(dead_code)] +/// This function is used for testing only, which creates a trivial +/// instance-witness pair +#[cfg(test)] pub fn create_trivial_pair( x_len: usize, w_len: usize, @@ -79,6 +83,8 @@ pub fn create_trivial_pair( (trivial_witness, trivial_instance) } +/// Check that whether the witness and instance are satisfied R1CS. +/// (A ·Z) ◦ (B ·Z) = u ·(C ·Z) + E #[allow(dead_code)] pub fn is_r1cs_satisfied( r1cs: &R1CS, @@ -121,7 +127,6 @@ pub fn is_r1cs_satisfied( #[cfg(test)] - mod tests { use kzg::scheme::KzgScheme; use kzg::srs::Srs; diff --git a/nova/src/transcript.rs b/nova/src/transcript.rs index 255e9c2..feaa7fa 100644 --- a/nova/src/transcript.rs +++ b/nova/src/transcript.rs @@ -42,6 +42,15 @@ impl Transcript { challenge_parse } + /// Creates a new `Transcript` instance from a list of scalar number + /// + /// # Parameters + /// + /// - `kzg_commitment`: A slice containing the commitments. + /// + /// # Returns + /// + /// A new `Transcript` instance. pub fn from_scalar_number(numbers: &[ScalarField]) -> Self { let mut challenge_parse = Self::default(); for number in numbers { diff --git a/nova/src/utils.rs b/nova/src/utils.rs index 762247c..43b74f8 100644 --- a/nova/src/utils.rs +++ b/nova/src/utils.rs @@ -1,4 +1,15 @@ use ark_ff::PrimeField; + +/// Computes the product of a matrix and a vector. +/// +/// # Arguments +/// +/// * `matrix` - A matrix represented as a vector of vectors. +/// * `z` - A vector to be multiplied by the matrix. +/// +/// # Returns +/// +/// A vector resulting from the product of the matrix and the vector. #[allow(dead_code)] pub fn matrix_vector_product(matrix: &Vec>, z: &Vec) -> Vec { let mut r: Vec = vec![F::zero(); matrix.len()]; @@ -10,6 +21,16 @@ pub fn matrix_vector_product(matrix: &Vec>, z: &Vec) -> r } +/// Computes the Hadamard product of two vectors of equal size. +/// +/// # Arguments +/// +/// * `a` - The first vector. +/// * `b` - The second vector. +/// +/// # Returns +/// +/// A vector resulting from the Hadamard product of the two input vectors. #[allow(dead_code)] pub fn hadamard_product(a: &Vec, b: &Vec) -> Vec { @@ -20,6 +41,16 @@ pub fn hadamard_product(a: &Vec, b: &Vec) -> Vec { r } +/// Computes the product of an element and a vector. +/// +/// # Arguments +/// +/// * `a` - The vector. +/// * `u` - The element to be multiplied with the vector. +/// +/// # Returns +/// +/// A vector resulting from multiplying each element of `a` by `u`. #[allow(dead_code)] pub fn vector_elem_product(a: &Vec, u: F) -> Vec { let mut r: Vec = vec![F::zero(); a.len()]; @@ -29,6 +60,16 @@ pub fn vector_elem_product(a: &Vec, u: F) -> Vec { r } +/// Subtracts one vector from another. +/// +/// # Arguments +/// +/// * `a` - The first vector. +/// * `b` - The second vector. +/// +/// # Returns +/// +/// A vector resulting from subtracting `b` from `a`. #[allow(dead_code)] pub fn vec_sub(a: &Vec, b: &Vec) -> Vec { assert_eq!(a.len(), b.len()); @@ -39,6 +80,16 @@ pub fn vec_sub(a: &Vec, b: &Vec) -> Vec { r } +/// Adds two vectors. +/// +/// # Arguments +/// +/// * `a` - The first vector. +/// * `b` - The second vector. +/// +/// # Returns +/// +/// A vector resulting from adding `a` and `b`. #[allow(dead_code)] pub fn vec_add(a: &Vec, b: &Vec) -> Vec { assert_eq!(a.len(), b.len()); @@ -49,6 +100,16 @@ pub fn vec_add(a: &Vec, b: &Vec) -> Vec { r } +/// Checks if two vectors are equal. +/// +/// # Arguments +/// +/// * `a` - The first vector. +/// * `b` - The second vector. +/// +/// # Returns +/// +/// `true` if `a` and `b` are equal, `false` otherwise. #[allow(dead_code)] pub fn vec_equal(a: &Vec, b: &Vec) -> bool { if a.len() != b.len() { @@ -63,6 +124,15 @@ pub fn vec_equal(a: &Vec, b: &Vec) -> bool { true } +/// Converts a matrix of `usize` values to a matrix of `F` values. +/// +/// # Arguments +/// +/// * `matrix` - A matrix represented as a vector of vectors of `usize` values. +/// +/// # Returns +/// +/// A matrix represented as a vector of vectors of `F` values. #[allow(dead_code)] pub fn to_f_matrix (matrix: Vec>) -> Vec> { let mut r: Vec> = vec![Vec::new(); matrix.len()]; @@ -75,6 +145,15 @@ pub fn to_f_matrix (matrix: Vec>) -> Vec> { r } +/// Converts a vector of `usize` values to a vector of `F` values. +/// +/// # Arguments +/// +/// * `z` - A vector of `usize` values. +/// +/// # Returns +/// +/// A vector of `F` values. #[allow(dead_code)] pub fn to_f_vec(z: Vec) -> Vec { let mut r: Vec = vec![F::zero(); z.len()]; @@ -82,4 +161,4 @@ pub fn to_f_vec(z: Vec) -> Vec { r[i] = F::from(z[i] as u64); } r -} \ No newline at end of file +} From 2a5cad64cd7fd117452cc02d43536c04133c3c43 Mon Sep 17 00:00:00 2001 From: vanhger Date: Thu, 16 May 2024 08:31:40 +0700 Subject: [PATCH 17/18] feat: Add examples for Nova lib & Refine code. --- nova/Cargo.toml | 5 + nova/examples/examples.rs | 170 +++++++++++++++++++++++++++++++++ nova/nova.md | 2 + nova/src/ivc/mod.rs | 4 +- nova/src/lib.rs | 12 +-- nova/src/nifs/nifs_verifier.rs | 2 - nova/src/r1cs/mod.rs | 9 +- 7 files changed, 189 insertions(+), 15 deletions(-) create mode 100644 nova/examples/examples.rs create mode 100644 nova/nova.md diff --git a/nova/Cargo.toml b/nova/Cargo.toml index 59283d4..c4e4943 100644 --- a/nova/Cargo.toml +++ b/nova/Cargo.toml @@ -3,6 +3,11 @@ name = "nova" version = "0.1.0" edition = "2021" + +[[example]] +name = "nova-example" +path = "examples/examples.rs" + [dependencies] ark-ff = "0.4.2" ark-ec = "0.4.2" diff --git a/nova/examples/examples.rs b/nova/examples/examples.rs new file mode 100644 index 0000000..9df304b --- /dev/null +++ b/nova/examples/examples.rs @@ -0,0 +1,170 @@ +use ark_ff::{BigInteger, One, PrimeField, Zero}; +use sha2::Sha256; +use kzg::scheme::KzgScheme; +use kzg::srs::Srs; +use kzg::types::{BaseField, ScalarField}; +use nova::circuit::{AugmentedCircuit, FCircuit, State}; +use nova::ivc::{IVC, IVCProof, ZkIVCProof}; +use nova::r1cs::{create_trivial_pair, FInstance, FWitness, R1CS}; +use nova::transcript::Transcript; +use nova::utils::{to_f_matrix, to_f_vec}; + +struct TestCircuit {} +impl FCircuit for TestCircuit { + fn run(&self, z_i: &State, w_i: &FWitness) -> State { + let x = w_i.w[0].clone(); + let res = x * x * x + x + ScalarField::from(5); + let base_res = BaseField::from_le_bytes_mod_order(&res.into_bigint().to_bytes_le()); + + State { + state: z_i.state + base_res + } + } +} +fn main() { + // (x0^3 + x0 + 5) + (x1^3 + x1 + 5) + (x2^3 + x2 + 5) + (x3^3 + x2 + 5) = 130 + // x0 = 3, x1 = 4, x2 = 1, x3 = 2 + + // generate R1CS, witnesses and public input, output. + let (r1cs, witnesses, x) = gen_test_values::(vec![3, 4, 1, 2]); + let (matrix_a, _, _) = (r1cs.matrix_a.clone(), r1cs.matrix_b.clone(), r1cs.matrix_c.clone()); + + // Trusted setup + let domain_size = witnesses[0].len() + x[0].len() + 1; + let srs = Srs::new(domain_size); + let scheme = KzgScheme::new(srs); + let x_len = x[0].len(); + + // Generate witnesses and instances + let w: Vec = witnesses.iter().map(|witness| FWitness::new(witness, matrix_a.len())).collect(); + let mut u: Vec = w.iter().zip(x).map(|(w, x)| w.commit(&scheme, &x)).collect(); + + // step i + let mut i = BaseField::zero(); + + // generate trivial instance-witness pair + let (trivial_witness, trivial_instance) = create_trivial_pair(x_len, witnesses[0].len(), &scheme); + + // generate f_circuit instance + let f_circuit = TestCircuit{}; + + // generate states + let mut z = vec![State{state: BaseField::from(0)}; 5]; + for index in 1..5 { + z[index] = f_circuit.run(&z[index - 1], &w[index-1]); + } + + let mut prover_transcript; + let mut verifier_transcript = Transcript::::default(); + + // create F' + let augmented_circuit = AugmentedCircuit::::new( + f_circuit, + &trivial_instance, + &z[0] + ); + + // generate IVC + let mut ivc = IVC:: { + scheme, + augmented_circuit + }; + + // initialize IVC proof, zkIVCProof, folded witness and folded instance + let mut ivc_proof = IVCProof::trivial_ivc_proof(&trivial_instance, &trivial_witness); + let mut zk_ivc_proof = ZkIVCProof::trivial_zk_ivc_proof(&trivial_instance); + let mut folded_witness = trivial_witness.clone(); + let mut folded_instance = trivial_instance.clone(); + + let mut res; + for step in 0..4 { + println!("Step: {:?}", step); + if step == 0 { + res = ivc.augmented_circuit.run( + &u[step], + None, + &w[step], + None, + ); + } else { + res = ivc.augmented_circuit.run( + &ivc_proof.u_i, + Some(&ivc_proof.big_u_i.clone()), + &ivc_proof.w_i, + Some(&zk_ivc_proof.com_t.clone().unwrap()) + ); + } + + if res.is_err() { + println!("{:?}", res); + } + assert!(res.is_ok()); + + // verifier verify this step + let verify = ivc.verify(&zk_ivc_proof, &mut verifier_transcript); + if verify.is_err() { + println!("{:?}", verify); + } + assert!(verify.is_ok()); + + // update for next step + + if step != 3 { // do not update if we have done with IVC + ivc.augmented_circuit.next_step(); + i = i + BaseField::one(); + assert_eq!(ivc.augmented_circuit.z_i.state, z[step + 1].state); + prover_transcript = Transcript::::default(); + verifier_transcript = Transcript::::default(); + + let hash_x = AugmentedCircuit::::hash_io(i, &z[0], &z[step + 1], &folded_instance); + // convert u_1_x from BaseField into ScalarField + u[step + 1].x = vec![ScalarField::from_le_bytes_mod_order(&hash_x.into_bigint().to_bytes_le())]; + + // generate ivc_proof and zkSNARK proof. + ivc_proof = IVCProof::new(&u[step + 1], &w[step + 1], &folded_instance, &folded_witness); + (folded_witness, folded_instance, zk_ivc_proof) = ivc.prove(&r1cs, &ivc_proof, &mut prover_transcript); + } + } +} + +fn gen_test_values(inputs: Vec) -> (R1CS, Vec>, Vec>) { + // R1CS for: x^3 + x + 5 = y (example from article + // https://vitalik.eth.limo/general/2016/12/10/qap.html ) + + let a = to_f_matrix::(vec![ + vec![1, 0, 0, 0, 0, 0], + vec![0, 1, 0, 0, 0, 0], + vec![1, 0, 1, 0, 0, 0], + vec![0, 0, 0, 1, 0, 5], + ]); + let b = to_f_matrix::(vec![ + vec![1, 0, 0, 0, 0, 0], + vec![1, 0, 0, 0, 0, 0], + vec![0, 0, 0, 0, 0, 1], + vec![0, 0, 0, 0, 0, 1], + ]); + let c = to_f_matrix::(vec![ + vec![0, 1, 0, 0, 0, 0], + vec![0, 0, 1, 0, 0, 0], + vec![0, 0, 0, 1, 0, 0], + vec![0, 0, 0, 0, 1, 0], + ]); + + // generate n witnesses + let mut w: Vec> = Vec::new(); + let mut x: Vec> = Vec::new(); + for input in inputs { + let w_i = to_f_vec::(vec![ + input, + input * input, // x^2 + input * input * input, // x^2 * x + input * input * input + input, // x^3 + x + ]); + w.push(w_i.clone()); + let x_i = to_f_vec::(vec![input * input * input + input + 5]); // output: x^3 + x + 5 + x.push(x_i.clone()); + } + + let r1cs = R1CS:: { matrix_a: a, matrix_b: b, matrix_c: c, num_io: 1, num_vars: 4 }; + (r1cs, w, x) +} \ No newline at end of file diff --git a/nova/nova.md b/nova/nova.md new file mode 100644 index 0000000..6ab159c --- /dev/null +++ b/nova/nova.md @@ -0,0 +1,2 @@ +This is a simple implementation of [NOVA](https://eprint.iacr.org/2021/370.pdf). + diff --git a/nova/src/ivc/mod.rs b/nova/src/ivc/mod.rs index 0fe27fa..bcc79b1 100644 --- a/nova/src/ivc/mod.rs +++ b/nova/src/ivc/mod.rs @@ -74,7 +74,7 @@ impl ZkIVCProof { /// IVC structure includes a scheme for commitment and an augmented F' function pub struct IVC > { - pub(crate) scheme: KzgScheme, - pub(crate) augmented_circuit: AugmentedCircuit, + pub scheme: KzgScheme, + pub augmented_circuit: AugmentedCircuit, } diff --git a/nova/src/lib.rs b/nova/src/lib.rs index c3f5a7e..5011401 100644 --- a/nova/src/lib.rs +++ b/nova/src/lib.rs @@ -1,6 +1,6 @@ -mod nifs; -mod utils; -mod transcript; -mod ivc; -mod circuit; -mod r1cs; \ No newline at end of file +pub mod nifs; +pub mod utils; +pub mod transcript; +pub mod ivc; +pub mod circuit; +pub mod r1cs; \ No newline at end of file diff --git a/nova/src/nifs/nifs_verifier.rs b/nova/src/nifs/nifs_verifier.rs index 9646fe1..ccf9087 100644 --- a/nova/src/nifs/nifs_verifier.rs +++ b/nova/src/nifs/nifs_verifier.rs @@ -137,8 +137,6 @@ pub fn gen_test_values(inputs: Vec) -> (R1CS, Vec(vec![input * input * input + input + 5]); // output: x^3 + x + 5 x.push(x_i.clone()); } - // println!("w: {:?}", w); - // println!("x: {:?}", x); let r1cs = R1CS:: { matrix_a: a, matrix_b: b, matrix_c: c, num_io: 1, num_vars: 4 }; (r1cs, w, x) diff --git a/nova/src/r1cs/mod.rs b/nova/src/r1cs/mod.rs index 3964b8d..cb3b8e5 100644 --- a/nova/src/r1cs/mod.rs +++ b/nova/src/r1cs/mod.rs @@ -61,17 +61,16 @@ impl FWitness { // cW.0 = cW.0.mul(self.rW).into_affine(); FInstance { - com_e: com_e, + com_e, u: ScalarField::one(), - com_w: com_w, + com_w, x: x.clone(), } } } -/// This function is used for testing only, which creates a trivial -/// instance-witness pair -#[cfg(test)] +/// This function creates a trivial instance-witness pair +#[allow(dead_code)] pub fn create_trivial_pair( x_len: usize, w_len: usize, From d72354977b2dd07237d6c7bb638a42f10c9abca0 Mon Sep 17 00:00:00 2001 From: vanhger Date: Mon, 22 Jul 2024 14:57:36 +0700 Subject: [PATCH 18/18] refactor: Refine code --- Cargo.toml | 2 +- kzg/src/scheme.rs | 40 +++++- nova/examples/examples.rs | 107 +++++++++------ nova/src/circuit.rs | 241 ++++++++++++++++++++------------- nova/src/ivc/ivc_prover.rs | 35 +++-- nova/src/ivc/ivc_verifier.rs | 210 ++++++++++++++++------------ nova/src/ivc/mod.rs | 35 ++--- nova/src/lib.rs | 8 +- nova/src/nifs/mod.rs | 60 ++++---- nova/src/nifs/nifs_prover.rs | 54 ++++---- nova/src/nifs/nifs_verifier.rs | 99 +++++++++----- nova/src/r1cs/mod.rs | 57 ++++---- nova/src/transcript.rs | 24 ++-- nova/src/utils.rs | 19 ++- 14 files changed, 579 insertions(+), 412 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9ca5024..1e2f63f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["plonk", "kzg", "fri"] +members = ["plonk", "kzg", "fri", "nova"] resolver = "2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html \ No newline at end of file diff --git a/kzg/src/scheme.rs b/kzg/src/scheme.rs index 252cfa0..92530e0 100644 --- a/kzg/src/scheme.rs +++ b/kzg/src/scheme.rs @@ -6,13 +6,14 @@ use ark_ec::pairing::Pairing; use ark_ec::short_weierstrass::Affine; use ark_ec::{AffineRepr, CurveGroup}; use ark_ff::{One, Zero}; +use ark_poly::univariate::DensePolynomial; use ark_poly::{DenseUVPolynomial, Polynomial}; use rand::{Rng, RngCore}; use crate::commitment::KzgCommitment; use crate::opening::KzgOpening; use crate::srs::Srs; -use crate::types::{G1Point, Poly}; +use crate::types::{G1Point, Poly, ScalarField}; /// Implements the KZG polynomial commitment scheme. /// @@ -50,6 +51,21 @@ impl KzgScheme { KzgCommitment(commitment) } + /// Commits to a coefficient vector using the KZG scheme. + /// + /// # Parameters + /// + /// - `coeffs`: The coefficient vector to be committed to. + /// + /// # Returns + /// + /// The commitment to the polynomial. + pub fn commit_vector(&self, coeffs: &[ScalarField]) -> KzgCommitment { + let new_poly = DensePolynomial::from_coefficients_vec(coeffs.into()); + let commitment = self.evaluate_in_s(&new_poly); + KzgCommitment(commitment) + } + /// Commits to a parameter using the KZG scheme. /// /// # Parameters @@ -103,6 +119,28 @@ impl KzgScheme { KzgOpening(opening, evaluation_at_z) } + /// Opens a commitment at a specified point. + /// + /// # Parameters + /// + /// - `coeffs`: The coefficient vector to be opened. + /// - `z`: The point at which the polynomial is opened. + /// + /// # Returns + /// + /// The opening at the specified point. + pub fn open_vector(&self, coeffs: &[ScalarField], z: impl Into) -> KzgOpening { + let z = z.into(); + let mut polynomial = DensePolynomial::from_coefficients_vec(coeffs.into()); + let evaluation_at_z = polynomial.evaluate(&z); + let first = polynomial.coeffs.first_mut().expect("at least 1"); + *first -= evaluation_at_z; + let root = Poly::from_coefficients_slice(&[-z, 1.into()]); + let quotient_poly = &polynomial / &root; + let opening = self.evaluate_in_s("ient_poly); + KzgOpening(opening, evaluation_at_z) + } + /// Verifies the correctness of an opening. /// /// # Parameters diff --git a/nova/examples/examples.rs b/nova/examples/examples.rs index 9df304b..1ede90f 100644 --- a/nova/examples/examples.rs +++ b/nova/examples/examples.rs @@ -1,23 +1,23 @@ use ark_ff::{BigInteger, One, PrimeField, Zero}; -use sha2::Sha256; use kzg::scheme::KzgScheme; use kzg::srs::Srs; use kzg::types::{BaseField, ScalarField}; use nova::circuit::{AugmentedCircuit, FCircuit, State}; -use nova::ivc::{IVC, IVCProof, ZkIVCProof}; +use nova::ivc::{IVCProof, ZkIVCProof, IVC}; use nova::r1cs::{create_trivial_pair, FInstance, FWitness, R1CS}; use nova::transcript::Transcript; use nova::utils::{to_f_matrix, to_f_vec}; +use sha2::Sha256; struct TestCircuit {} impl FCircuit for TestCircuit { fn run(&self, z_i: &State, w_i: &FWitness) -> State { - let x = w_i.w[0].clone(); + let x = w_i.w[0]; let res = x * x * x + x + ScalarField::from(5); let base_res = BaseField::from_le_bytes_mod_order(&res.into_bigint().to_bytes_le()); State { - state: z_i.state + base_res + state: z_i.state + base_res, } } } @@ -27,7 +27,11 @@ fn main() { // generate R1CS, witnesses and public input, output. let (r1cs, witnesses, x) = gen_test_values::(vec![3, 4, 1, 2]); - let (matrix_a, _, _) = (r1cs.matrix_a.clone(), r1cs.matrix_b.clone(), r1cs.matrix_c.clone()); + let (matrix_a, _, _) = ( + r1cs.matrix_a.clone(), + r1cs.matrix_b.clone(), + r1cs.matrix_c.clone(), + ); // Trusted setup let domain_size = witnesses[0].len() + x[0].len() + 1; @@ -36,38 +40,48 @@ fn main() { let x_len = x[0].len(); // Generate witnesses and instances - let w: Vec = witnesses.iter().map(|witness| FWitness::new(witness, matrix_a.len())).collect(); - let mut u: Vec = w.iter().zip(x).map(|(w, x)| w.commit(&scheme, &x)).collect(); + let w: Vec = witnesses + .iter() + .map(|witness| FWitness::new(witness, matrix_a.len())) + .collect(); + let mut u: Vec = w + .iter() + .zip(x) + .map(|(w, x)| w.commit(&scheme, &x)) + .collect(); // step i let mut i = BaseField::zero(); // generate trivial instance-witness pair - let (trivial_witness, trivial_instance) = create_trivial_pair(x_len, witnesses[0].len(), &scheme); + let (trivial_witness, trivial_instance) = + create_trivial_pair(x_len, witnesses[0].len(), &scheme); // generate f_circuit instance - let f_circuit = TestCircuit{}; + let f_circuit = TestCircuit {}; // generate states - let mut z = vec![State{state: BaseField::from(0)}; 5]; + let mut z = vec![ + State { + state: BaseField::from(0) + }; + 5 + ]; for index in 1..5 { - z[index] = f_circuit.run(&z[index - 1], &w[index-1]); + z[index] = f_circuit.run(&z[index - 1], &w[index - 1]); } let mut prover_transcript; let mut verifier_transcript = Transcript::::default(); // create F' - let augmented_circuit = AugmentedCircuit::::new( - f_circuit, - &trivial_instance, - &z[0] - ); + let augmented_circuit = + AugmentedCircuit::::new(f_circuit, &trivial_instance, &z[0]); // generate IVC let mut ivc = IVC:: { scheme, - augmented_circuit + augmented_circuit, }; // initialize IVC proof, zkIVCProof, folded witness and folded instance @@ -80,18 +94,13 @@ fn main() { for step in 0..4 { println!("Step: {:?}", step); if step == 0 { - res = ivc.augmented_circuit.run( - &u[step], - None, - &w[step], - None, - ); + res = ivc.augmented_circuit.run(&u[step], None, &w[step], None); } else { res = ivc.augmented_circuit.run( &ivc_proof.u_i, Some(&ivc_proof.big_u_i.clone()), &ivc_proof.w_i, - Some(&zk_ivc_proof.com_t.clone().unwrap()) + Some(&zk_ivc_proof.com_t.clone().unwrap()), ); } @@ -109,20 +118,34 @@ fn main() { // update for next step - if step != 3 { // do not update if we have done with IVC + if step != 3 { + // do not update if we have done with IVC ivc.augmented_circuit.next_step(); - i = i + BaseField::one(); + i += BaseField::one(); assert_eq!(ivc.augmented_circuit.z_i.state, z[step + 1].state); prover_transcript = Transcript::::default(); verifier_transcript = Transcript::::default(); - let hash_x = AugmentedCircuit::::hash_io(i, &z[0], &z[step + 1], &folded_instance); + let hash_x = AugmentedCircuit::::hash_io( + i, + &z[0], + &z[step + 1], + &folded_instance, + ); // convert u_1_x from BaseField into ScalarField - u[step + 1].x = vec![ScalarField::from_le_bytes_mod_order(&hash_x.into_bigint().to_bytes_le())]; + u[step + 1].x = vec![ScalarField::from_le_bytes_mod_order( + &hash_x.into_bigint().to_bytes_le(), + )]; // generate ivc_proof and zkSNARK proof. - ivc_proof = IVCProof::new(&u[step + 1], &w[step + 1], &folded_instance, &folded_witness); - (folded_witness, folded_instance, zk_ivc_proof) = ivc.prove(&r1cs, &ivc_proof, &mut prover_transcript); + ivc_proof = IVCProof::new( + &u[step + 1], + &w[step + 1], + &folded_instance, + &folded_witness, + ); + (folded_witness, folded_instance, zk_ivc_proof) = + ivc.prove(&r1cs, &ivc_proof, &mut prover_transcript); } } } @@ -131,19 +154,19 @@ fn gen_test_values(inputs: Vec) -> (R1CS, Vec>, // R1CS for: x^3 + x + 5 = y (example from article // https://vitalik.eth.limo/general/2016/12/10/qap.html ) - let a = to_f_matrix::(vec![ + let a = to_f_matrix::(&[ vec![1, 0, 0, 0, 0, 0], vec![0, 1, 0, 0, 0, 0], vec![1, 0, 1, 0, 0, 0], vec![0, 0, 0, 1, 0, 5], ]); - let b = to_f_matrix::(vec![ + let b = to_f_matrix::(&[ vec![1, 0, 0, 0, 0, 0], vec![1, 0, 0, 0, 0, 0], vec![0, 0, 0, 0, 0, 1], vec![0, 0, 0, 0, 0, 1], ]); - let c = to_f_matrix::(vec![ + let c = to_f_matrix::(&[ vec![0, 1, 0, 0, 0, 0], vec![0, 0, 1, 0, 0, 0], vec![0, 0, 0, 1, 0, 0], @@ -156,15 +179,21 @@ fn gen_test_values(inputs: Vec) -> (R1CS, Vec>, for input in inputs { let w_i = to_f_vec::(vec![ input, - input * input, // x^2 - input * input * input, // x^2 * x - input * input * input + input, // x^3 + x + input * input, // x^2 + input * input * input, // x^2 * x + input * input * input + input, // x^3 + x ]); w.push(w_i.clone()); - let x_i = to_f_vec::(vec![input * input * input + input + 5]); // output: x^3 + x + 5 + let x_i = to_f_vec::(vec![input * input * input + input + 5]); // output: x^3 + x + 5 x.push(x_i.clone()); } - let r1cs = R1CS:: { matrix_a: a, matrix_b: b, matrix_c: c, num_io: 1, num_vars: 4 }; + let r1cs = R1CS:: { + matrix_a: a, + matrix_b: b, + matrix_c: c, + num_io: 1, + num_vars: 4, + }; (r1cs, w, x) -} \ No newline at end of file +} diff --git a/nova/src/circuit.rs b/nova/src/circuit.rs index 7028b2f..a557352 100644 --- a/nova/src/circuit.rs +++ b/nova/src/circuit.rs @@ -1,14 +1,13 @@ -use std::marker::PhantomData; -use std::ops::Add; +use crate::nifs::NIFS; +use crate::r1cs::{FInstance, FWitness}; +use crate::transcript::Transcript; use ark_ff::{BigInteger, One, PrimeField, Zero}; use ark_serialize::CanonicalSerialize; -use sha2::Digest; use kzg::commitment::KzgCommitment; use kzg::types::{BaseField, ScalarField}; -use crate::nifs::{NIFS}; -use crate::r1cs::{FInstance, FWitness}; -use crate::transcript::Transcript; - +use sha2::Digest; +use std::marker::PhantomData; +use std::ops::Add; /// State structure of IVC, which is presented in BaseField of Bsn12_381 curve /// Todo: Implement a general version. @@ -23,7 +22,6 @@ pub trait FCircuit { fn run(&self, z_i: &State, w_i: &FWitness) -> State; } - /// F' circuit pub struct AugmentedCircuit { // F function @@ -46,13 +44,8 @@ pub struct AugmentedCircuit AugmentedCircuit { - - pub fn new( - f_circuit: FC, - trivial_instance: &FInstance, - z_0: &State, - ) -> Self { +impl AugmentedCircuit { + pub fn new(f_circuit: FC, trivial_instance: &FInstance, z_0: &State) -> Self { Self { f_circuit, i: BaseField::zero(), @@ -62,7 +55,7 @@ impl AugmentedCircui z_i1: None, h_i: None, h_i1: None, - phantom_data_t: PhantomData + phantom_data_t: PhantomData, } } pub fn run( @@ -71,10 +64,8 @@ impl AugmentedCircui big_u_i: Option<&FInstance>, w_i: &FWitness, com_t: Option<&KzgCommitment>, - ) -> Result{ - + ) -> Result { if self.i != BaseField::from(0) { - // check that if i > 0 then U_i and com_t must exist if big_u_i.is_none() || com_t.is_none() { return Err(String::from("Wrong parameters.")); @@ -82,18 +73,18 @@ impl AugmentedCircui // check that if i > 0 then the hash_x must exist if self.h_i.is_none() { - return Err(String::from("The hash public IO must exist")) + return Err(String::from("The hash public IO must exist")); } // get hash_x - let hash_x = self.h_i.clone().unwrap(); + let hash_x = self.h_i.unwrap(); // 1. check that u.x =? hash_x // Because u_i.x is in ScalarField while hash_x is in BaseField, they need to // be converted into a comparable type // Todo: Non-native field transform - let u_dot_x = u_i.x[0].clone(); + let u_dot_x = u_i.x[0]; let hash_fr = ScalarField::from_le_bytes_mod_order(&hash_x.into_bigint().to_bytes_le()); if u_dot_x != hash_fr { return Err(String::from("Public IO is wrong ")); @@ -122,20 +113,21 @@ impl AugmentedCircui let z_i1 = self.f_circuit.run(&self.z_i, w_i); // compute hash - let new_hash= Self::hash_io(self.i.add(BaseField::one()), &self.z_0, &z_i1, &big_u_i1); + let new_hash = Self::hash_io(self.i.add(BaseField::one()), &self.z_0, &z_i1, &big_u_i1); // store the next hash self.h_i1 = Some(new_hash); // store the next state self.z_i1 = Some(z_i1); - - } else { // i == 0 + } else { + // i == 0 // compute z_1 = F(z_0, w_i) let z_i1 = self.f_circuit.run(&self.z_i, w_i); // compute hash - let new_hash = Self::hash_io(BaseField::one(), &self.z_0, &z_i1, &self.trivial_instance); + let new_hash = + Self::hash_io(BaseField::one(), &self.z_0, &z_i1, &self.trivial_instance); // store the next hash self.h_i1 = Some(new_hash); @@ -144,25 +136,20 @@ impl AugmentedCircui } // 4. output the hash - return Ok(self.h_i1.unwrap()); + Ok(self.h_i1.unwrap()) } - /// updating F' function for the next step of IVC. + /// Updating F' function for the next step of IVC. pub fn next_step(&mut self) { self.z_i = self.z_i1.clone().unwrap(); self.z_i1 = None; - self.i = self.i + BaseField::one(); + self.i += BaseField::one(); self.h_i = self.h_i1; self.h_i1 = None; } /// A function computes public IO of an instance: u.x = hash(i, z0, zi, Ui). - pub fn hash_io( - i: BaseField, - z_0: &State, - z_i: &State, - big_u_i: &FInstance - ) -> BaseField { + pub fn hash_io(i: BaseField, z_0: &State, z_i: &State, big_u_i: &FInstance) -> BaseField { let mut hasher = T::default(); i.serialize_uncompressed(&mut hasher).unwrap(); z_0.state.serialize_uncompressed(&mut hasher).unwrap(); @@ -184,23 +171,23 @@ impl AugmentedCircui #[cfg(test)] #[allow(dead_code)] mod test { - use sha2::Sha256; - use kzg::scheme::KzgScheme; - use kzg::srs::Srs; + use super::*; use crate::nifs::nifs_verifier::gen_test_values; use crate::r1cs::create_trivial_pair; - use super::*; + use kzg::scheme::KzgScheme; + use kzg::srs::Srs; + use sha2::Sha256; struct TestCircuit {} impl FCircuit for TestCircuit { fn run(&self, z_i: &State, w_i: &FWitness) -> State { - let x = w_i.w[0].clone(); + let x = w_i.w[0]; let res = x * x * x + x + ScalarField::from(5); // because res is in scalar field, we need to convert it into base_field let base_res = BaseField::from_le_bytes_mod_order(&res.into_bigint().to_bytes_le()); State { - state: z_i.state + base_res + state: z_i.state + base_res, } } } @@ -209,7 +196,11 @@ mod test { fn test_augmented_circuit_01() { // generate R1CS, witnesses and public input, output. let (r1cs, witnesses, x) = gen_test_values::(vec![3]); - let (matrix_a, _, _) = (r1cs.matrix_a.clone(), r1cs.matrix_b.clone(), r1cs.matrix_c.clone()); + let (matrix_a, _, _) = ( + r1cs.matrix_a.clone(), + r1cs.matrix_b.clone(), + r1cs.matrix_c.clone(), + ); // Trusted setup let domain_size = witnesses[0].len() + x[0].len() + 1; @@ -223,11 +214,15 @@ mod test { let (_, trivial_instance) = create_trivial_pair(x[0].len(), witnesses[0].len(), &scheme); // generate f_circuit instance - let f_circuit = TestCircuit{}; + let f_circuit = TestCircuit {}; // generate state - let z_0 = State{state: BaseField::from(0)}; - let z_1 = State{state: BaseField::from(35)}; + let z_0 = State { + state: BaseField::from(0), + }; + let z_1 = State { + state: BaseField::from(35), + }; // let prover_transcript = Transcript::::default(); // create F' @@ -240,25 +235,28 @@ mod test { z_i1: None, h_i: None, h_i1: None, - phantom_data_t: PhantomData + phantom_data_t: PhantomData, }; - let res1 = augmented_circuit.run( - &u_0, - None, - &w_0, - None, - ); + let res1 = augmented_circuit.run(&u_0, None, &w_0, None); // check if F' is running if res1.is_err() { - println!("{:?}",res1); + println!("{:?}", res1); } assert!(res1.is_ok()); let hash = res1.unwrap(); // check if the hash output is correct - assert_eq!(hash, AugmentedCircuit::::hash_io(BaseField::one(), &z_0, &z_1, &trivial_instance)); + assert_eq!( + hash, + AugmentedCircuit::::hash_io( + BaseField::one(), + &z_0, + &z_1, + &trivial_instance + ) + ); augmented_circuit.next_step(); // check if the state produced is correct assert_eq!(augmented_circuit.z_i.state, z_1.state); @@ -268,7 +266,11 @@ mod test { fn test_augmented_circuit_02() { // generate R1CS, witnesses and public input, output. let (r1cs, witnesses, x) = gen_test_values::(vec![3, 4]); - let (matrix_a, _, _) = (r1cs.matrix_a.clone(), r1cs.matrix_b.clone(), r1cs.matrix_c.clone()); + let (matrix_a, _, _) = ( + r1cs.matrix_a.clone(), + r1cs.matrix_b.clone(), + r1cs.matrix_c.clone(), + ); // Trusted setup let domain_size = witnesses[0].len() + x[0].len() + 1; @@ -279,22 +281,44 @@ mod test { let mut u_1 = w_1.commit(&scheme, &x[1]); // generate trivial_instance - let (trivial_witness, trivial_instance) = create_trivial_pair(x[0].len(), witnesses[0].len(), &scheme); + let (trivial_witness, trivial_instance) = + create_trivial_pair(x[0].len(), witnesses[0].len(), &scheme); // generate f_circuit instance - let f_circuit = TestCircuit{}; + let f_circuit = TestCircuit {}; // generate state - let z_0 = State{state: BaseField::from(0)}; - let z_1 = State{state: BaseField::from(35)}; - let z_2 = State{state: BaseField::from(108)}; + let z_0 = State { + state: BaseField::from(0), + }; + let z_1 = State { + state: BaseField::from(35), + }; + let z_2 = State { + state: BaseField::from(108), + }; let mut prover_transcript = Transcript::::default(); - let u_1_x = AugmentedCircuit::::hash_io(BaseField::from(1), &z_0, &z_1, &trivial_instance); + let u_1_x = AugmentedCircuit::::hash_io( + BaseField::from(1), + &z_0, + &z_1, + &trivial_instance, + ); // convert u_1_x from BaseField into ScalarField - u_1.x = vec![ScalarField::from_le_bytes_mod_order(&u_1_x.into_bigint().to_bytes_le())]; + u_1.x = vec![ScalarField::from_le_bytes_mod_order( + &u_1_x.into_bigint().to_bytes_le(), + )]; - let (_, folded_instance, com_t, _) = NIFS::::prover(&r1cs, &w_1, &trivial_witness, &u_1, &trivial_instance, &scheme, &mut prover_transcript); + let (_, folded_instance, com_t, _) = NIFS::::prover( + &r1cs, + &w_1, + &trivial_witness, + &u_1, + &trivial_instance, + &scheme, + &mut prover_transcript, + ); // create F' let mut augmented_circuit = AugmentedCircuit:: { @@ -306,39 +330,43 @@ mod test { z_i1: None, h_i: Some(u_1_x), h_i1: None, - phantom_data_t: PhantomData + phantom_data_t: PhantomData, }; - - - let res1 = augmented_circuit.run( - &u_1, - Some(&trivial_instance), - &w_1, - Some(&com_t) - ); + let res1 = augmented_circuit.run(&u_1, Some(&trivial_instance), &w_1, Some(&com_t)); // check if F' is running if res1.is_err() { - println!("{:?}",res1); + println!("{:?}", res1); } assert!(res1.is_ok()); let hash = res1.unwrap(); // check if the hash output is correct - assert_eq!(hash, AugmentedCircuit::::hash_io(BaseField::from(2), &z_0, &z_2, &folded_instance)); + assert_eq!( + hash, + AugmentedCircuit::::hash_io( + BaseField::from(2), + &z_0, + &z_2, + &folded_instance + ) + ); augmented_circuit.next_step(); // check if the state produced is correct assert_eq!(augmented_circuit.z_i.state, z_2.state); } - #[test] #[should_panic] fn test_augmented_circuit_03() { // generate R1CS, witnesses and public input, output. let (r1cs, witnesses, x) = gen_test_values::(vec![3, 4]); - let (matrix_a, _, _) = (r1cs.matrix_a.clone(), r1cs.matrix_b.clone(), r1cs.matrix_c.clone()); + let (matrix_a, _, _) = ( + r1cs.matrix_a.clone(), + r1cs.matrix_b.clone(), + r1cs.matrix_c.clone(), + ); // Trusted setup let domain_size = witnesses[0].len() + x[0].len() + 1; @@ -349,22 +377,44 @@ mod test { let mut u_1 = w_1.commit(&scheme, &x[1]); // generate trivial_instance - let (trivial_witness, trivial_instance) = create_trivial_pair(x[0].len(), witnesses[0].len(), &scheme); + let (trivial_witness, trivial_instance) = + create_trivial_pair(x[0].len(), witnesses[0].len(), &scheme); // generate f_circuit instance - let f_circuit = TestCircuit{}; + let f_circuit = TestCircuit {}; // generate state - let z_0 = State{state: BaseField::from(0)}; - let z_1 = State{state: BaseField::from(35)}; - let z_2 = State{state: BaseField::from(130)}; + let z_0 = State { + state: BaseField::from(0), + }; + let z_1 = State { + state: BaseField::from(35), + }; + let z_2 = State { + state: BaseField::from(130), + }; let mut prover_transcript = Transcript::::default(); - let u_1_x = AugmentedCircuit::::hash_io(BaseField::from(1), &z_0, &z_1, &trivial_instance); + let u_1_x = AugmentedCircuit::::hash_io( + BaseField::from(1), + &z_0, + &z_1, + &trivial_instance, + ); // convert u_1_x from BaseField into ScalarField - u_1.x = vec![ScalarField::from_le_bytes_mod_order(&u_1_x.into_bigint().to_bytes_le())]; + u_1.x = vec![ScalarField::from_le_bytes_mod_order( + &u_1_x.into_bigint().to_bytes_le(), + )]; - let (_, folded_instance, com_t, _) = NIFS::::prover(&r1cs, &w_1, &trivial_witness, &u_1, &trivial_instance, &scheme, &mut prover_transcript); + let (_, folded_instance, com_t, _) = NIFS::::prover( + &r1cs, + &w_1, + &trivial_witness, + &u_1, + &trivial_instance, + &scheme, + &mut prover_transcript, + ); // create F' let mut augmented_circuit = AugmentedCircuit:: { @@ -376,27 +426,28 @@ mod test { z_i1: None, h_i: Some(u_1_x), h_i1: None, - phantom_data_t: PhantomData + phantom_data_t: PhantomData, }; - - - let res1 = augmented_circuit.run( - &u_1, - Some(&trivial_instance), - &w_1, - Some(&com_t) - ); + let res1 = augmented_circuit.run(&u_1, Some(&trivial_instance), &w_1, Some(&com_t)); // check if F' is running if res1.is_err() { - println!("{:?}",res1); + println!("{:?}", res1); } assert!(res1.is_ok()); let hash = res1.unwrap(); // check if the hash output is correct - assert_eq!(hash, AugmentedCircuit::::hash_io(BaseField::from(2), &z_0, &z_2, &folded_instance)); + assert_eq!( + hash, + AugmentedCircuit::::hash_io( + BaseField::from(2), + &z_0, + &z_2, + &folded_instance + ) + ); augmented_circuit.next_step(); // check if the state produced is correct assert_eq!(augmented_circuit.z_i.state, z_2.state); diff --git a/nova/src/ivc/ivc_prover.rs b/nova/src/ivc/ivc_prover.rs index 08dea2d..2f96124 100644 --- a/nova/src/ivc/ivc_prover.rs +++ b/nova/src/ivc/ivc_prover.rs @@ -1,15 +1,14 @@ -use ark_ff::{Zero}; -use sha2::Digest; -use kzg::types::{ScalarField}; -use crate::circuit::{FCircuit}; -use crate::ivc::{IVC, IVCProof, ZkIVCProof}; +use crate::circuit::FCircuit; +use crate::ivc::{IVCProof, ZkIVCProof, IVC}; use crate::nifs::NIFS; use crate::r1cs::{FInstance, FWitness, R1CS}; use crate::transcript::Transcript; +use ark_ff::Zero; +use kzg::types::ScalarField; +use sha2::Digest; #[allow(dead_code)] -impl IVC { - +impl IVC { /// IVC prover will fold 2 instance-witness pairs into one via NIFS /// and generate zkSNARK proof for it. pub fn prove( @@ -18,13 +17,11 @@ impl IVC { ivc_proof: &IVCProof, prover_transcript: &mut Transcript, ) -> (FWitness, FInstance, ZkIVCProof) { - let i = self.augmented_circuit.i; - if ! i.is_zero() { - + if !i.is_zero() { // 1 + 2. Parse Π and compute U', W' and com_T let (big_w_out, big_u_out, com_t, r) = NIFS::::prover( - &r1cs, + r1cs, &ivc_proof.w_i, &ivc_proof.big_w_i, &ivc_proof.u_i, @@ -34,7 +31,8 @@ impl IVC { ); // 3. Generate zkSNARK proof - let nifs_proof = NIFS::::prove(r, &big_w_out, &big_u_out, &self.scheme, prover_transcript); + let nifs_proof = + NIFS::::prove(r, &big_w_out, &big_u_out, &self.scheme, prover_transcript); ( big_w_out, @@ -43,9 +41,9 @@ impl IVC { u_i: ivc_proof.u_i.clone(), big_u_i: ivc_proof.big_u_i.clone(), com_t: Some(com_t), - folded_u_proof: Some(nifs_proof) - }) - + folded_u_proof: Some(nifs_proof), + }, + ) } else { ( ivc_proof.big_w_i.clone(), @@ -54,8 +52,9 @@ impl IVC { u_i: ivc_proof.u_i.clone(), big_u_i: ivc_proof.big_u_i.clone(), com_t: None, - folded_u_proof: None - }) + folded_u_proof: None, + }, + ) } } -} \ No newline at end of file +} diff --git a/nova/src/ivc/ivc_verifier.rs b/nova/src/ivc/ivc_verifier.rs index f24cea0..43e379f 100644 --- a/nova/src/ivc/ivc_verifier.rs +++ b/nova/src/ivc/ivc_verifier.rs @@ -1,41 +1,39 @@ -use ark_ff::{BigInteger, One, PrimeField, Zero}; -use sha2::Digest; -use kzg::types::{BaseField, ScalarField}; use crate::circuit::{AugmentedCircuit, FCircuit}; -use crate::ivc::{IVC, ZkIVCProof}; +use crate::ivc::{ZkIVCProof, IVC}; use crate::nifs::NIFS; use crate::transcript::Transcript; +use ark_ff::{BigInteger, One, PrimeField, Zero}; +use kzg::types::{BaseField, ScalarField}; +use sha2::Digest; #[allow(dead_code)] -impl IVC { - +impl IVC { /// IVC verifier will do 5 steps as mentioned in constructor 4 /// of Nova paper. pub fn verify( &mut self, zk_ivc_proof: &ZkIVCProof, - verifier_transcript: &mut Transcript + verifier_transcript: &mut Transcript, ) -> Result<(), String> { - let i = self.augmented_circuit.i; let z_0 = &self.augmented_circuit.z_0; let z_i = &self.augmented_circuit.z_i; - if i == BaseField::zero() { - return if z_0.state == z_i.state { + if z_0.state == z_i.state { Ok(()) } else { Err(String::from("Verify failed: wrong state")) } } else { - // 1. parse zkIVCProof = (U, u, comT, nifs_proof) let u_i = zk_ivc_proof.u_i.clone(); let big_u_i = zk_ivc_proof.big_u_i.clone(); if zk_ivc_proof.com_t.is_none() { - return Err(String::from("Verify failed: commitment of cross term T must exist")); + return Err(String::from( + "Verify failed: commitment of cross term T must exist", + )); } if zk_ivc_proof.folded_u_proof.is_none() { @@ -46,7 +44,8 @@ impl IVC { // 2. check that u.x = hash(i, z_0, z_i, U) let hash_io = AugmentedCircuit::::hash_io(i, z_0, z_i, &big_u_i); - let hash_fr = ScalarField::from_le_bytes_mod_order(&hash_io.into_bigint().to_bytes_le()); + let hash_fr = + ScalarField::from_le_bytes_mod_order(&hash_io.into_bigint().to_bytes_le()); if u_i.x[0] != hash_fr { return Err(String::from("Verify failed: Public IO is wrong")); } @@ -55,7 +54,7 @@ impl IVC { if u_i.com_e != self.augmented_circuit.trivial_instance.com_e { return Err(String::from("Verify failed: Commitment of E is wrong")); } - if ! u_i.u.is_one() { + if !u_i.u.is_one() { return Err(String::from("Verify failed: Scalar u is wrong")); } @@ -63,10 +62,16 @@ impl IVC { let big_u_out = NIFS::::verifier(folded_u_proof.r, &u_i, &big_u_i, &com_t); // 5. verify that zkSNARK.V(U', pi_U') = 1 - let res = NIFS::::verify(&folded_u_proof, &u_i, &big_u_i, &big_u_out, &com_t, &self.scheme, verifier_transcript); - - res + NIFS::::verify( + &folded_u_proof, + &u_i, + &big_u_i, + &big_u_out, + &com_t, + &self.scheme, + verifier_transcript, + ) } } } @@ -74,26 +79,26 @@ impl IVC { #[cfg(test)] #[allow(dead_code)] mod tests { - use std::marker::PhantomData; - use sha2::Sha256; - use kzg::scheme::KzgScheme; - use kzg::srs::Srs; + use super::*; + use crate::circuit::State; use crate::ivc::IVCProof; use crate::nifs::nifs_verifier::gen_test_values; - use super::*; use crate::r1cs::{create_trivial_pair, FInstance, FWitness}; use crate::transcript::Transcript; - use crate::circuit::State; + use kzg::scheme::KzgScheme; + use kzg::srs::Srs; + use sha2::Sha256; + use std::marker::PhantomData; struct TestCircuit {} impl FCircuit for TestCircuit { fn run(&self, z_i: &State, w_i: &FWitness) -> State { - let x = w_i.w[0].clone(); + let x = w_i.w[0]; let res = x * x * x + x + ScalarField::from(5); // because res is in scalar field, we need to convert it into base_field let base_res = BaseField::from_le_bytes_mod_order(&res.into_bigint().to_bytes_le()); State { - state: z_i.state + base_res + state: z_i.state + base_res, } } } @@ -105,7 +110,11 @@ mod tests { // generate R1CS, witnesses and public input, output. let (r1cs, witnesses, x) = gen_test_values::(vec![3, 4, 1, 2]); - let (matrix_a, _, _) = (r1cs.matrix_a.clone(), r1cs.matrix_b.clone(), r1cs.matrix_c.clone()); + let (matrix_a, _, _) = ( + r1cs.matrix_a.clone(), + r1cs.matrix_b.clone(), + r1cs.matrix_c.clone(), + ); // Trusted setup let domain_size = witnesses[0].len() + x[0].len() + 1; @@ -114,39 +123,48 @@ mod tests { let x_len = x[0].len(); // Generate witnesses and instances - let w: Vec = witnesses.iter().map(|witness| FWitness::new(witness, matrix_a.len())).collect(); - let mut u: Vec = w.iter().zip(x).map(|(w, x)| w.commit(&scheme, &x)).collect(); + let w: Vec = witnesses + .iter() + .map(|witness| FWitness::new(witness, matrix_a.len())) + .collect(); + let mut u: Vec = w + .iter() + .zip(x) + .map(|(w, x)| w.commit(&scheme, &x)) + .collect(); // step i let mut i = BaseField::zero(); // generate trivial instance-witness pair - let (trivial_witness, trivial_instance) = create_trivial_pair(x_len, witnesses[0].len(), &scheme); + let (trivial_witness, trivial_instance) = + create_trivial_pair(x_len, witnesses[0].len(), &scheme); // generate f_circuit instance - let f_circuit = TestCircuit{}; + let f_circuit = TestCircuit {}; // generate states - let mut z = vec![State{state: BaseField::from(0)}; 5]; + let mut z = vec![ + State { + state: BaseField::from(0) + }; + 5 + ]; for index in 1..5 { - z[index] = f_circuit.run(&z[index - 1], &w[index-1]); + z[index] = f_circuit.run(&z[index - 1], &w[index - 1]); } let mut prover_transcript; let mut verifier_transcript = Transcript::::default(); - // create F' - let augmented_circuit = AugmentedCircuit::::new( - f_circuit, - &trivial_instance, - &z[0] - ); + let augmented_circuit = + AugmentedCircuit::::new(f_circuit, &trivial_instance, &z[0]); // generate IVC let mut ivc = IVC:: { scheme, - augmented_circuit + augmented_circuit, }; // initialize IVC proof, zkIVCProof, folded witness and folded instance @@ -157,22 +175,16 @@ mod tests { let mut res; for step in 0..4 { - println!("Step: {:?}", step); if step == 0 { - res = ivc.augmented_circuit.run( - &u[step], - None, - &w[step], - None, - ); + res = ivc.augmented_circuit.run(&u[step], None, &w[step], None); } else { res = ivc.augmented_circuit.run( &ivc_proof.u_i, Some(&ivc_proof.big_u_i.clone()), &ivc_proof.w_i, - Some(&zk_ivc_proof.com_t.clone().unwrap()) + Some(&zk_ivc_proof.com_t.clone().unwrap()), ); } @@ -190,20 +202,34 @@ mod tests { // update for next step - if step != 3 { // do not update if we have done with IVC + if step != 3 { + // do not update if we have done with IVC ivc.augmented_circuit.next_step(); - i = i + BaseField::one(); + i += BaseField::one(); assert_eq!(ivc.augmented_circuit.z_i.state, z[step + 1].state); prover_transcript = Transcript::::default(); verifier_transcript = Transcript::::default(); - let hash_x = AugmentedCircuit::::hash_io(i, &z[0], &z[step + 1], &folded_instance); + let hash_x = AugmentedCircuit::::hash_io( + i, + &z[0], + &z[step + 1], + &folded_instance, + ); // convert u_1_x from BaseField into ScalarField - u[step + 1].x = vec![ScalarField::from_le_bytes_mod_order(&hash_x.into_bigint().to_bytes_le())]; + u[step + 1].x = vec![ScalarField::from_le_bytes_mod_order( + &hash_x.into_bigint().to_bytes_le(), + )]; // generate ivc_proof and zkSNARK proof. - ivc_proof = IVCProof::new(&u[step + 1], &w[step + 1], &folded_instance, &folded_witness); - (folded_witness, folded_instance, zk_ivc_proof) = ivc.prove(&r1cs, &ivc_proof, &mut prover_transcript); + ivc_proof = IVCProof::new( + &u[step + 1], + &w[step + 1], + &folded_instance, + &folded_witness, + ); + (folded_witness, folded_instance, zk_ivc_proof) = + ivc.prove(&r1cs, &ivc_proof, &mut prover_transcript); } } } @@ -216,7 +242,11 @@ mod tests { // generate R1CS, witnesses and public input, output. let (r1cs, witnesses, x) = gen_test_values::(vec![3, 4, 1]); - let (matrix_a, _, _) = (r1cs.matrix_a.clone(), r1cs.matrix_b.clone(), r1cs.matrix_c.clone()); + let (matrix_a, _, _) = ( + r1cs.matrix_a.clone(), + r1cs.matrix_b.clone(), + r1cs.matrix_c.clone(), + ); // Trusted setup let domain_size = witnesses[0].len() + x[0].len() + 1; @@ -240,10 +270,12 @@ mod tests { let trivial_instance = trivial_witness.commit(&scheme, &trivial_x); // generate f_circuit instance - let f_circuit = TestCircuit{}; + let f_circuit = TestCircuit {}; // generate state - let z_0 = State{state: BaseField::from(0)}; + let z_0 = State { + state: BaseField::from(0), + }; let z_1 = f_circuit.run(&z_0, &w_0); let z_2 = f_circuit.run(&z_1, &w_1); @@ -260,13 +292,13 @@ mod tests { z_i1: None, h_i: None, h_i1: None, - phantom_data_t: PhantomData + phantom_data_t: PhantomData, }; // generate IVC let mut ivc = IVC:: { scheme, - augmented_circuit + augmented_circuit, }; // initialize IVC proof, zkIVCProof, folded witness (W) and folded instance (U) @@ -284,19 +316,13 @@ mod tests { folded_u_proof: None, }; - let folded_witness; let folded_instance; println!("Step 1"); // run F' for the first time // With i = 0, the instance U_i and commitment com_t do not exist. - let res1 = ivc.augmented_circuit.run( - &u_0, - None, - &w_0, - None, - ); + let res1 = ivc.augmented_circuit.run(&u_0, None, &w_0, None); if res1.is_err() { println!("Step: {:?}, {:?}", i, res1); @@ -312,21 +338,24 @@ mod tests { // update for next step ivc.augmented_circuit.next_step(); - i = i + BaseField::one(); + i += BaseField::one(); assert_eq!(ivc.augmented_circuit.z_i.state, BaseField::from(35)); prover_transcript = Transcript::::default(); verifier_transcript = Transcript::::default(); // because all instances above are from F, not F', so we need to do this trick. - let u_1_x = AugmentedCircuit::::hash_io(i, &z_0, &z_1, &trivial_instance); + let u_1_x = + AugmentedCircuit::::hash_io(i, &z_0, &z_1, &trivial_instance); // convert u_1_x from BaseField into ScalarField - u_1.x = vec![ScalarField::from_le_bytes_mod_order(&u_1_x.into_bigint().to_bytes_le())]; + u_1.x = vec![ScalarField::from_le_bytes_mod_order( + &u_1_x.into_bigint().to_bytes_le(), + )]; // U_1 is a trivial instance (via the paper). // Prover fold u_1 and U_1 into U_2. // generate IVC proof. - ivc_proof = IVCProof{ + ivc_proof = IVCProof { u_i: u_1.clone(), w_i: w_1, big_u_i: trivial_instance.clone(), @@ -334,11 +363,8 @@ mod tests { }; // generate W_2, U_2 and zkIVCProof via IVC proof - (folded_witness, folded_instance, zk_ivc_proof) = ivc.prove( - &r1cs, - &ivc_proof, - &mut prover_transcript - ); + (folded_witness, folded_instance, zk_ivc_proof) = + ivc.prove(&r1cs, &ivc_proof, &mut prover_transcript); println!("Step 2"); // run F' for the second time @@ -346,7 +372,7 @@ mod tests { &ivc_proof.u_i, Some(&ivc_proof.big_u_i.clone()), &ivc_proof.w_i, - Some(&zk_ivc_proof.com_t.clone().unwrap()) + Some(&zk_ivc_proof.com_t.clone().unwrap()), ); if res2.is_err() { @@ -363,28 +389,30 @@ mod tests { // update next step ivc.augmented_circuit.next_step(); - i = i + BaseField::one(); + i += BaseField::one(); prover_transcript = Transcript::::default(); verifier_transcript = Transcript::::default(); // check if this state is 108. - assert_eq!(ivc.augmented_circuit.z_i.state, BaseField::from(108), "Wrong state"); - + assert_eq!( + ivc.augmented_circuit.z_i.state, + BaseField::from(108), + "Wrong state" + ); - let u_2_x = AugmentedCircuit::::hash_io(i, &z_0, &z_2, &folded_instance); - u_2.x = vec![ScalarField::from_le_bytes_mod_order(&u_2_x.into_bigint().to_bytes_le())]; + let u_2_x = + AugmentedCircuit::::hash_io(i, &z_0, &z_2, &folded_instance); + u_2.x = vec![ScalarField::from_le_bytes_mod_order( + &u_2_x.into_bigint().to_bytes_le(), + )]; ivc_proof = IVCProof { u_i: u_2, w_i: w_2, big_u_i: folded_instance, // U_2 - big_w_i: folded_witness // W_2 + big_w_i: folded_witness, // W_2 }; // Compute W_3, U_3, and zkSNARK proof - (_, _, zk_ivc_proof) = ivc.prove( - &r1cs, - &ivc_proof, - &mut prover_transcript - ); + (_, _, zk_ivc_proof) = ivc.prove(&r1cs, &ivc_proof, &mut prover_transcript); println!("Step 3"); // run F' for the last time @@ -392,7 +420,7 @@ mod tests { &ivc_proof.u_i, Some(&ivc_proof.big_u_i.clone()), &ivc_proof.w_i, - Some(&zk_ivc_proof.com_t.clone().unwrap()) + Some(&zk_ivc_proof.com_t.clone().unwrap()), ); if res3.is_err() { @@ -410,6 +438,10 @@ mod tests { // update next step ivc.augmented_circuit.next_step(); // check if this state is 115. - assert_eq!(ivc.augmented_circuit.z_i.state, BaseField::from(115), "Wrong state"); + assert_eq!( + ivc.augmented_circuit.z_i.state, + BaseField::from(115), + "Wrong state" + ); } -} \ No newline at end of file +} diff --git a/nova/src/ivc/mod.rs b/nova/src/ivc/mod.rs index bcc79b1..eb6747c 100644 --- a/nova/src/ivc/mod.rs +++ b/nova/src/ivc/mod.rs @@ -1,12 +1,12 @@ mod ivc_prover; mod ivc_verifier; -use sha2::Digest; -use kzg::commitment::KzgCommitment; -use kzg::scheme::KzgScheme; use crate::circuit::{AugmentedCircuit, FCircuit}; -use crate::nifs::{NIFSProof}; +use crate::nifs::NIFSProof; use crate::r1cs::{FInstance, FWitness}; +use kzg::commitment::KzgCommitment; +use kzg::scheme::KzgScheme; +use sha2::Digest; /// This struct is the zero knowledge proof for IVC /// π = (U, u, com_T , π_U') where U' is the folded instance @@ -28,53 +28,40 @@ pub struct IVCProof { #[allow(dead_code)] impl IVCProof { - - pub fn new( - u_i: &FInstance, - w_i: &FWitness, - big_u_i: &FInstance, - big_w_i: &FWitness, - ) -> Self { + pub fn new(u_i: &FInstance, w_i: &FWitness, big_u_i: &FInstance, big_w_i: &FWitness) -> Self { Self { u_i: u_i.clone(), w_i: w_i.clone(), big_u_i: big_u_i.clone(), - big_w_i: big_w_i.clone() + big_w_i: big_w_i.clone(), } } // Generate a trivial IVC proof. - pub fn trivial_ivc_proof( - trivial_instance: &FInstance, - trivial_witness: &FWitness, - ) -> Self { + pub fn trivial_ivc_proof(trivial_instance: &FInstance, trivial_witness: &FWitness) -> Self { Self { u_i: trivial_instance.clone(), w_i: trivial_witness.clone(), big_u_i: trivial_instance.clone(), - big_w_i: trivial_witness.clone() + big_w_i: trivial_witness.clone(), } } } #[allow(dead_code)] impl ZkIVCProof { - pub fn trivial_zk_ivc_proof( - trivial_instance: &FInstance, - ) -> Self { + pub fn trivial_zk_ivc_proof(trivial_instance: &FInstance) -> Self { Self { u_i: trivial_instance.clone(), big_u_i: trivial_instance.clone(), com_t: None, - folded_u_proof: None + folded_u_proof: None, } } - } /// IVC structure includes a scheme for commitment and an augmented F' function -pub struct IVC > { +pub struct IVC { pub scheme: KzgScheme, pub augmented_circuit: AugmentedCircuit, } - diff --git a/nova/src/lib.rs b/nova/src/lib.rs index 5011401..8b65ecd 100644 --- a/nova/src/lib.rs +++ b/nova/src/lib.rs @@ -1,6 +1,6 @@ +pub mod circuit; +pub mod ivc; pub mod nifs; -pub mod utils; +pub mod r1cs; pub mod transcript; -pub mod ivc; -pub mod circuit; -pub mod r1cs; \ No newline at end of file +pub mod utils; diff --git a/nova/src/nifs/mod.rs b/nova/src/nifs/mod.rs index 8ad05f7..75e3553 100644 --- a/nova/src/nifs/mod.rs +++ b/nova/src/nifs/mod.rs @@ -1,17 +1,18 @@ +use ark_ec::CurveGroup; use std::marker::PhantomData; use std::ops::Mul; -use ark_ec::CurveGroup; +use crate::r1cs::{FInstance, FWitness, R1CS}; +use crate::utils::{ + hadamard_product, matrix_vector_product, vec_add, vec_sub, vector_elem_product, +}; use kzg::commitment::KzgCommitment; -use kzg::types::ScalarField; -use sha2::{Digest}; use kzg::opening::KzgOpening; -use crate::r1cs::{FInstance, FWitness, R1CS}; -use crate::utils::{hadamard_product, matrix_vector_product, vec_add, vec_sub, vector_elem_product}; +use kzg::types::ScalarField; +use sha2::Digest; -pub(crate) mod nifs_verifier; mod nifs_prover; - +pub(crate) mod nifs_verifier; /// NIFS Proof is a zk proof. To convince the verifier, prover creates an opening /// for each E and W. @@ -20,25 +21,23 @@ pub struct NIFSProof { pub r: ScalarField, pub opening_point: ScalarField, pub opening_e: KzgOpening, - pub opening_w: KzgOpening + pub opening_w: KzgOpening, } pub struct NIFS { _phantom_data_t: PhantomData, } -impl NIFS { - +impl NIFS { /// Compute the cross-term T /// T = AZ1 ◦ BZ2 + AZ2 ◦ BZ1 − u1 · CZ2 − u2 · CZ1. pub fn compute_t( r1cs: &R1CS, u1: ScalarField, u2: ScalarField, - z1: &Vec, - z2: &Vec + z1: &[ScalarField], + z2: &[ScalarField], ) -> Vec { - let az1 = matrix_vector_product(&r1cs.matrix_a, z1); let bz1 = matrix_vector_product(&r1cs.matrix_b, z1); let cz1 = matrix_vector_product(&r1cs.matrix_c, z1); @@ -65,22 +64,20 @@ impl NIFS { r: ScalarField, fw1: &FWitness, fw2: &FWitness, - t: &Vec, + t: &[ScalarField], // rT: ScalarField, ) -> FWitness { + let new_e = fw1 + .e + .iter() + .zip(t.iter()) + .zip(&fw2.e) + .map(|((e1, t), e2)| *e1 + r * *t + r * r * *e2) + .collect(); - let new_e = fw1.e.iter().zip(t.iter()).zip(&fw2.e).map(|((e1, t), e2)| { - *e1 + r * *t + r * r * *e2 - }).collect(); - - let new_w = fw1.w.iter().zip(&fw2.w).map(|(a, b)| { - *a + *b * r - }).collect(); + let new_w = fw1.w.iter().zip(&fw2.w).map(|(a, b)| *a + *b * r).collect(); - FWitness{ - e: new_e, - w: new_w - } + FWitness { e: new_e, w: new_w } } /// Fold two instances into one. @@ -94,21 +91,18 @@ impl NIFS { fi2: &FInstance, com_t: &KzgCommitment, ) -> FInstance { - let new_com_e = KzgCommitment((fi1.com_e.0 + com_t.0.mul(r) + fi2.com_e.0.mul(r * r)).into_affine()); + let new_com_e = + KzgCommitment((fi1.com_e.0 + com_t.0.mul(r) + fi2.com_e.0.mul(r * r)).into_affine()); let new_com_w = KzgCommitment((fi1.com_w.0 + fi2.com_w.0.mul(r)).into_affine()); let new_u = fi1.u + fi2.u * r; - let new_x = fi1.x.iter().zip(&fi2.x).map(|(a, b)| { - *a + *b * r - }).collect(); + let new_x = fi1.x.iter().zip(&fi2.x).map(|(a, b)| *a + *b * r).collect(); - FInstance{ + FInstance { com_e: new_com_e, u: new_u, com_w: new_com_w, - x: new_x + x: new_x, } } - } - diff --git a/nova/src/nifs/nifs_prover.rs b/nova/src/nifs/nifs_prover.rs index e70c597..764dbce 100644 --- a/nova/src/nifs/nifs_prover.rs +++ b/nova/src/nifs/nifs_prover.rs @@ -1,14 +1,12 @@ -use sha2::Digest; +use crate::nifs::{FInstance, FWitness, NIFSProof, NIFS}; +use crate::r1cs::R1CS; +use crate::transcript::Transcript; use kzg::commitment::KzgCommitment; use kzg::scheme::KzgScheme; use kzg::types::ScalarField; -use crate::nifs::{FInstance, FWitness, NIFS, NIFSProof}; -use crate::r1cs::R1CS; -use crate::transcript::Transcript; - - -impl NIFS { +use sha2::Digest; +impl NIFS { /// Prover output a folded instance-witness pair, com_T and challenge r via Fiat-Shamir pub fn prover( r1cs: &R1CS, @@ -17,9 +15,8 @@ impl NIFS { fi1: &FInstance, fi2: &FInstance, scheme: &KzgScheme, - transcript: &mut Transcript + transcript: &mut Transcript, ) -> (FWitness, FInstance, KzgCommitment, ScalarField) { - // generate Z = (W, x, u) let mut z1 = fw1.w.clone(); z1.append(&mut fi1.x.clone()); @@ -29,7 +26,7 @@ impl NIFS { z2.append(&mut fi2.x.clone()); z2.push(fi2.u); - let t = NIFS::::compute_t(&r1cs, fi1.u, fi2.u, &z1, &z2); + let t = NIFS::::compute_t(r1cs, fi1.u, fi2.u, &z1, &z2); let com_t = scheme.commit_vector(&t); transcript.feed_scalar_num(fi1.u); @@ -51,7 +48,6 @@ impl NIFS { scheme: &KzgScheme, transcript: &mut Transcript, ) -> NIFSProof { - // opening = Transcript(fi_cmE, fi_cmW); transcript.feed(&fi.com_e); transcript.feed(&fi.com_w); @@ -64,7 +60,7 @@ impl NIFS { r, opening_point, opening_e, - opening_w + opening_w, } } } @@ -72,17 +68,21 @@ impl NIFS { #[cfg(test)] mod test { - use sha2::Sha256; - use kzg::srs::Srs; + use super::*; use crate::nifs::nifs_verifier::gen_test_values; use crate::r1cs::is_r1cs_satisfied; - use super::*; + use kzg::srs::Srs; + use sha2::Sha256; #[test] pub fn test_prover_folding() { // generate R1CS, witnesses and public input, output. let (r1cs, witnesses, x) = gen_test_values::(vec![3, 4]); - let (matrix_a, _, _) = (r1cs.matrix_a.clone(), r1cs.matrix_b.clone(), r1cs.matrix_c.clone()); + let (matrix_a, _, _) = ( + r1cs.matrix_a.clone(), + r1cs.matrix_b.clone(), + r1cs.matrix_c.clone(), + ); // Trusted setup let domain_size = witnesses[0].len() + x[0].len() + 1; @@ -90,20 +90,22 @@ mod test { let scheme = KzgScheme::new(srs); // Generate witnesses and instances - let w: Vec = witnesses.iter().map(|witness| FWitness::new(witness, matrix_a.len())).collect(); - let u: Vec = w.iter().zip(x).map(|(w, x)| w.commit(&scheme, &x)).collect(); + let w: Vec = witnesses + .iter() + .map(|witness| FWitness::new(witness, matrix_a.len())) + .collect(); + let u: Vec = w + .iter() + .zip(x) + .map(|(w, x)| w.commit(&scheme, &x)) + .collect(); let mut transcript = Transcript::::default(); - let (folded_witness, folded_instance, _, _) = NIFS::::prover( - &r1cs, - &w[0], &w[1], - &u[0], &u[1], - &scheme, - &mut transcript - ); + let (folded_witness, folded_instance, _, _) = + NIFS::::prover(&r1cs, &w[0], &w[1], &u[0], &u[1], &scheme, &mut transcript); - let ok = is_r1cs_satisfied(&r1cs,&folded_instance, &folded_witness, &scheme); + let ok = is_r1cs_satisfied(&r1cs, &folded_instance, &folded_witness, &scheme); if ok.is_err() { println!("{:?}", ok); diff --git a/nova/src/nifs/nifs_verifier.rs b/nova/src/nifs/nifs_verifier.rs index ccf9087..03537c1 100644 --- a/nova/src/nifs/nifs_verifier.rs +++ b/nova/src/nifs/nifs_verifier.rs @@ -1,15 +1,13 @@ +use crate::nifs::{FInstance, NIFSProof, NIFS, R1CS}; +use crate::transcript::Transcript; +use crate::utils::{to_f_matrix, to_f_vec}; use ark_ff::PrimeField; -use sha2::Digest; use kzg::commitment::KzgCommitment; use kzg::scheme::KzgScheme; use kzg::types::ScalarField; -use crate::nifs::{FInstance, NIFS, R1CS, NIFSProof}; -use crate::transcript::Transcript; -use crate::utils::{to_f_matrix, to_f_vec}; - - -impl NIFS { +use sha2::Digest; +impl NIFS { /// NIFS.V generate the folded instance. pub fn verifier( r: ScalarField, @@ -29,20 +27,15 @@ impl NIFS { fi3: &FInstance, // folded instance. com_t: &KzgCommitment, scheme: &KzgScheme, - transcript: &mut Transcript + transcript: &mut Transcript, ) -> Result<(), String> { - // verify challenge. let mut res = Self::verify_challenge(proof.r, fi1.u, fi2.u, com_t, transcript); - if res.is_err() { - return res; - } + res.as_ref()?; // verify opening. res = Self::verify_opening(proof, fi3, scheme, transcript); - if res.is_err() { - return res; - } + res.as_ref()?; Ok(()) } @@ -53,18 +46,18 @@ impl NIFS { fi1_u: ScalarField, fi2_u: ScalarField, com_t: &KzgCommitment, - transcript: &mut Transcript + transcript: &mut Transcript, ) -> Result<(), String> { // Recreate challenge r transcript.feed_scalar_num(fi1_u); transcript.feed_scalar_num(fi2_u); - transcript.feed(&com_t); + transcript.feed(com_t); let [new_r] = transcript.generate_challenges(); // Verify that proof.r = Transcript(fi1.u, fi2.u, cmT) if new_r != r { - return Err(String::from("Verify: Error in computing random r")) + return Err(String::from("Verify: Error in computing random r")); } Ok(()) @@ -75,14 +68,16 @@ impl NIFS { proof: &NIFSProof, fi3: &FInstance, // folded instance. scheme: &KzgScheme, - transcript: &mut Transcript + transcript: &mut Transcript, ) -> Result<(), String> { transcript.feed(&fi3.com_e); transcript.feed(&fi3.com_w); // Verify Opening_point = Transcript(fi1.cmE, fi1.cmW) let [opening_point] = transcript.generate_challenges(); if opening_point != proof.opening_point { - return Err(String::from("Verify: Error in computing random opening point")); + return Err(String::from( + "Verify: Error in computing random opening point", + )); } // Verify opening @@ -104,19 +99,19 @@ pub fn gen_test_values(inputs: Vec) -> (R1CS, Vec(vec![ + let a = to_f_matrix::(&[ vec![1, 0, 0, 0, 0, 0], vec![0, 1, 0, 0, 0, 0], vec![1, 0, 1, 0, 0, 0], vec![0, 0, 0, 1, 0, 5], ]); - let b = to_f_matrix::(vec![ + let b = to_f_matrix::(&[ vec![1, 0, 0, 0, 0, 0], vec![1, 0, 0, 0, 0, 0], vec![0, 0, 0, 0, 0, 1], vec![0, 0, 0, 0, 0, 1], ]); - let c = to_f_matrix::(vec![ + let c = to_f_matrix::(&[ vec![0, 1, 0, 0, 0, 0], vec![0, 0, 1, 0, 0, 0], vec![0, 0, 0, 1, 0, 0], @@ -129,16 +124,22 @@ pub fn gen_test_values(inputs: Vec) -> (R1CS, Vec(vec![ input, - input * input, // x^2 - input * input * input, // x^2 * x - input * input * input + input, // x^3 + x + input * input, // x^2 + input * input * input, // x^2 * x + input * input * input + input, // x^3 + x ]); w.push(w_i.clone()); - let x_i = to_f_vec::(vec![input * input * input + input + 5]); // output: x^3 + x + 5 + let x_i = to_f_vec::(vec![input * input * input + input + 5]); // output: x^3 + x + 5 x.push(x_i.clone()); } - let r1cs = R1CS:: { matrix_a: a, matrix_b: b, matrix_c: c, num_io: 1, num_vars: 4 }; + let r1cs = R1CS:: { + matrix_a: a, + matrix_b: b, + matrix_c: c, + num_io: 1, + num_vars: 4, + }; (r1cs, w, x) } @@ -146,15 +147,19 @@ pub fn gen_test_values(inputs: Vec) -> (R1CS, Vec::prove(r, &p_folded_witness, &p_folded_instance, &scheme, &mut prover_transcript); + let (p_folded_witness, p_folded_instance, com_t, r) = NIFS::prover( + &r1cs, + &fw1, + &fw2, + &fi1, + &fi2, + &scheme, + &mut prover_transcript, + ); + + let proof = NIFS::::prove( + r, + &p_folded_witness, + &p_folded_instance, + &scheme, + &mut prover_transcript, + ); let v_folded_instance = NIFS::::verifier(r, &fi1, &fi2, &com_t); - let result = NIFS::::verify(&proof, &fi1, &fi2, &v_folded_instance, &com_t, &scheme, &mut verifier_transcript); + let result = NIFS::::verify( + &proof, + &fi1, + &fi2, + &v_folded_instance, + &com_t, + &scheme, + &mut verifier_transcript, + ); println!("{:?}", result); assert!(result.is_ok()); } -} \ No newline at end of file +} diff --git a/nova/src/r1cs/mod.rs b/nova/src/r1cs/mod.rs index cb3b8e5..4589083 100644 --- a/nova/src/r1cs/mod.rs +++ b/nova/src/r1cs/mod.rs @@ -1,8 +1,10 @@ -use ark_ff::{PrimeField, Zero, One}; +use crate::utils::{ + hadamard_product, matrix_vector_product, vec_add, vec_equal, vector_elem_product, +}; +use ark_ff::{One, PrimeField, Zero}; use kzg::commitment::KzgCommitment; use kzg::scheme::KzgScheme; use kzg::types::ScalarField; -use crate::utils::{hadamard_product, matrix_vector_product, vec_add, vec_equal, vector_elem_product}; /// Create R1CS structure #[derive(Clone)] @@ -24,7 +26,6 @@ pub struct FInstance { pub x: Vec, } - /// Create Committed Relaxed FWitness with KZG commitment /// Todo: Need to implement a general-curve commitment #[derive(Debug, Clone)] @@ -36,11 +37,11 @@ pub struct FWitness { } #[allow(dead_code)] impl FWitness { - pub fn new(w: &Vec, len: usize) -> Self { + pub fn new(w: &[ScalarField], len: usize) -> Self { FWitness { e: vec![ScalarField::zero(); len], // rE: ScalarField::rand(&mut rand::thread_rng()), - w: w.clone(), + w: w.into(), // rW: ScalarField::rand(&mut rand::thread_rng()), } } @@ -54,7 +55,7 @@ impl FWitness { } /// Commit a witness into its corresponding instance. - pub fn commit(&self, scheme: &KzgScheme, x: &Vec) -> FInstance { + pub fn commit(&self, scheme: &KzgScheme, x: &[ScalarField]) -> FInstance { let com_e = scheme.commit_vector(&self.e); // cE.0 = cE.0.mul(self.rE).into_affine(); let com_w = scheme.commit_vector(&self.w); @@ -64,7 +65,7 @@ impl FWitness { com_e, u: ScalarField::one(), com_w, - x: x.clone(), + x: x.into(), } } } @@ -74,11 +75,11 @@ impl FWitness { pub fn create_trivial_pair( x_len: usize, w_len: usize, - scheme: &KzgScheme) --> (FWitness, FInstance){ + scheme: &KzgScheme, +) -> (FWitness, FInstance) { let trivial_x = vec![ScalarField::from(0); x_len]; let trivial_witness = FWitness::new_trivial_witness(w_len); - let trivial_instance = trivial_witness.commit(&scheme, &trivial_x); + let trivial_instance = trivial_witness.commit(scheme, &trivial_x); (trivial_witness, trivial_instance) } @@ -89,7 +90,7 @@ pub fn is_r1cs_satisfied( r1cs: &R1CS, f_instance: &FInstance, f_witness: &FWitness, - scheme: &KzgScheme + scheme: &KzgScheme, ) -> Result<(), String> { if r1cs.num_vars != f_witness.w.len() { return Err(String::from("Witness does not match with matrices")); @@ -114,30 +115,33 @@ pub fn is_r1cs_satisfied( let res_eq = vec_equal(&left_side, &right_side); // check whether Instance satisfies Witness - let res_com = (f_instance.com_w == scheme.commit_vector(&f_witness.w)) && (f_instance.com_e == scheme.commit_vector(&f_witness.e)); + let res_com = (f_instance.com_w == scheme.commit_vector(&f_witness.w)) + && (f_instance.com_e == scheme.commit_vector(&f_witness.e)); if res_com && res_eq { Ok(()) } else { - return Err(String::from("Instance does not satisfy the Witness.")) + Err(String::from("Instance does not satisfy the Witness.")) } - } - #[cfg(test)] mod tests { + use crate::nifs::nifs_verifier::gen_test_values; + use crate::r1cs::{is_r1cs_satisfied, FInstance, FWitness}; use kzg::scheme::KzgScheme; use kzg::srs::Srs; use kzg::types::ScalarField; - use crate::nifs::nifs_verifier::gen_test_values; - use crate::r1cs::{FInstance, FWitness, is_r1cs_satisfied}; #[test] pub fn test_r1cs_satisfaction_condition() { // generate R1CS, witnesses and public input, output. let (r1cs, witnesses, x) = gen_test_values::(vec![3]); - let (matrix_a, _, _) = (r1cs.matrix_a.clone(), r1cs.matrix_b.clone(), r1cs.matrix_c.clone()); + let (matrix_a, _, _) = ( + r1cs.matrix_a.clone(), + r1cs.matrix_b.clone(), + r1cs.matrix_c.clone(), + ); // Trusted setup let domain_size = witnesses[0].len() + x[0].len() + 1; @@ -145,14 +149,21 @@ mod tests { let scheme = KzgScheme::new(srs); // Generate witnesses and instances - let w: Vec = witnesses.iter().map(|witness| FWitness::new(witness, matrix_a.len())).collect(); - let u: Vec = w.iter().zip(x).map(|(w, x)| w.commit(&scheme, &x)).collect(); - - let ok = is_r1cs_satisfied(&r1cs,&u[0], &w[0], &scheme); + let w: Vec = witnesses + .iter() + .map(|witness| FWitness::new(witness, matrix_a.len())) + .collect(); + let u: Vec = w + .iter() + .zip(x) + .map(|(w, x)| w.commit(&scheme, &x)) + .collect(); + + let ok = is_r1cs_satisfied(&r1cs, &u[0], &w[0], &scheme); if ok.is_err() { println!("{:?}", ok); } assert!(ok.is_ok()); } -} \ No newline at end of file +} diff --git a/nova/src/transcript.rs b/nova/src/transcript.rs index feaa7fa..6014387 100644 --- a/nova/src/transcript.rs +++ b/nova/src/transcript.rs @@ -1,7 +1,7 @@ use std::marker::PhantomData; use ark_bls12_381::Fr; -use ark_ff::{UniformRand}; +use ark_ff::UniformRand; use ark_serialize::{CanonicalSerialize, Write}; use rand::rngs::StdRng; use rand::SeedableRng; @@ -81,8 +81,7 @@ impl Transcript { pub fn feed_scalar_num(&mut self, num: ScalarField) { let mut hasher = T::default(); hasher.update(self.data.take().unwrap_or_default()); - num - .serialize_uncompressed(HashMarshaller(&mut hasher)) + num.serialize_uncompressed(HashMarshaller(&mut hasher)) .expect("HashMarshaller::flush should be infallible!"); self.data = Some(hasher.finalize().to_vec()); self.generated = false; @@ -136,11 +135,10 @@ impl<'a, H: Digest> Write for HashMarshaller<'a, H> { mod tests { use std::ops::Mul; + use ark_bls12_381::Fr; use ark_ec::{AffineRepr, CurveGroup}; - use sha2::Sha256; use kzg::types::G1Point; - use ark_bls12_381::Fr; - + use sha2::Sha256; use super::*; @@ -153,8 +151,7 @@ mod tests { Transcript::::from_commitment(&commitments1).generate_challenges(); let commitments2: [KzgCommitment; 1] = [commitment2.clone()]; - let [b] = - Transcript::::from_commitment(&commitments2).generate_challenges(); + let [b] = Transcript::::from_commitment(&commitments2).generate_challenges(); assert_ne!(a, b, "should be different"); let commitments3: [KzgCommitment; 2] = [commitment1.clone(), commitment2.clone()]; @@ -170,8 +167,8 @@ mod tests { let a = ScalarField::from(15); let b = ScalarField::from(20); - let [x, y, z] = Transcript::::from_scalar_number(&vec![a, b]).generate_challenges(); - let [x1, y1, z1] = Transcript::::from_scalar_number(&vec![a, b]).generate_challenges(); + let [x, y, z] = Transcript::::from_scalar_number(&[a, b]).generate_challenges(); + let [x1, y1, z1] = Transcript::::from_scalar_number(&[a, b]).generate_challenges(); assert_eq!(x, x1, "should be equal"); assert_eq!(y, y1, "should be equal"); @@ -182,8 +179,10 @@ mod tests { fn transcript_test_02() { let a = ScalarField::from(15); let b = ScalarField::from(20); - let commitment1 = KzgCommitment(G1Point::generator().mul(ScalarField::from(1)).into_affine()); - let commitment2 = KzgCommitment(G1Point::generator().mul(ScalarField::from(2)).into_affine()); + let commitment1 = + KzgCommitment(G1Point::generator().mul(ScalarField::from(1)).into_affine()); + let commitment2 = + KzgCommitment(G1Point::generator().mul(ScalarField::from(2)).into_affine()); let mut ts1 = Transcript::::default(); let mut ts2 = Transcript::::default(); @@ -201,7 +200,6 @@ mod tests { assert_eq!(x, x1, "should be equal"); assert_eq!(y, y1, "should be equal"); assert_eq!(z, z1, "should be equal"); - } #[test] diff --git a/nova/src/utils.rs b/nova/src/utils.rs index 43b74f8..212f905 100644 --- a/nova/src/utils.rs +++ b/nova/src/utils.rs @@ -11,11 +11,11 @@ use ark_ff::PrimeField; /// /// A vector resulting from the product of the matrix and the vector. #[allow(dead_code)] -pub fn matrix_vector_product(matrix: &Vec>, z: &Vec) -> Vec { +pub fn matrix_vector_product(matrix: &[Vec], z: &[F]) -> Vec { let mut r: Vec = vec![F::zero(); matrix.len()]; for i in 0..matrix.len() { - for j in 0..matrix[i].len() { - r[i] += matrix[i][j] * z[j]; + for (j, z_j) in z.iter().enumerate().take(matrix[i].len()) { + r[i] += matrix[i][j] * z_j; } } r @@ -32,8 +32,7 @@ pub fn matrix_vector_product(matrix: &Vec>, z: &Vec) -> /// /// A vector resulting from the Hadamard product of the two input vectors. #[allow(dead_code)] -pub fn hadamard_product(a: &Vec, b: &Vec) -> Vec { - +pub fn hadamard_product(a: &[F], b: &[F]) -> Vec { let mut r: Vec = vec![F::zero(); a.len()]; for i in 0..a.len() { r[i] = a[i] * b[i]; @@ -52,7 +51,7 @@ pub fn hadamard_product(a: &Vec, b: &Vec) -> Vec { /// /// A vector resulting from multiplying each element of `a` by `u`. #[allow(dead_code)] -pub fn vector_elem_product(a: &Vec, u: F) -> Vec { +pub fn vector_elem_product(a: &[F], u: F) -> Vec { let mut r: Vec = vec![F::zero(); a.len()]; for i in 0..a.len() { r[i] = a[i] * u; @@ -71,7 +70,7 @@ pub fn vector_elem_product(a: &Vec, u: F) -> Vec { /// /// A vector resulting from subtracting `b` from `a`. #[allow(dead_code)] -pub fn vec_sub(a: &Vec, b: &Vec) -> Vec { +pub fn vec_sub(a: &[F], b: &[F]) -> Vec { assert_eq!(a.len(), b.len()); let mut r: Vec = vec![F::zero(); a.len()]; for i in 0..a.len() { @@ -91,7 +90,7 @@ pub fn vec_sub(a: &Vec, b: &Vec) -> Vec { /// /// A vector resulting from adding `a` and `b`. #[allow(dead_code)] -pub fn vec_add(a: &Vec, b: &Vec) -> Vec { +pub fn vec_add(a: &[F], b: &[F]) -> Vec { assert_eq!(a.len(), b.len()); let mut r: Vec = vec![F::zero(); a.len()]; for i in 0..a.len() { @@ -111,7 +110,7 @@ pub fn vec_add(a: &Vec, b: &Vec) -> Vec { /// /// `true` if `a` and `b` are equal, `false` otherwise. #[allow(dead_code)] -pub fn vec_equal(a: &Vec, b: &Vec) -> bool { +pub fn vec_equal(a: &[F], b: &[F]) -> bool { if a.len() != b.len() { return false; } @@ -134,7 +133,7 @@ pub fn vec_equal(a: &Vec, b: &Vec) -> bool { /// /// A matrix represented as a vector of vectors of `F` values. #[allow(dead_code)] -pub fn to_f_matrix (matrix: Vec>) -> Vec> { +pub fn to_f_matrix(matrix: &[Vec]) -> Vec> { let mut r: Vec> = vec![Vec::new(); matrix.len()]; for i in 0..matrix.len() { r[i] = vec![F::zero(); matrix[i].len()];