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/kzg/src/types.rs b/kzg/src/types.rs index 829ff04..ba7ea0f 100644 --- a/kzg/src/types.rs +++ b/kzg/src/types.rs @@ -5,4 +5,6 @@ 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/Cargo.toml b/nova/Cargo.toml new file mode 100644 index 0000000..c4e4943 --- /dev/null +++ b/nova/Cargo.toml @@ -0,0 +1,20 @@ +[package] +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" +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/examples/examples.rs b/nova/examples/examples.rs new file mode 100644 index 0000000..1ede90f --- /dev/null +++ b/nova/examples/examples.rs @@ -0,0 +1,199 @@ +use ark_ff::{BigInteger, One, PrimeField, Zero}; +use kzg::scheme::KzgScheme; +use kzg::srs::Srs; +use kzg::types::{BaseField, ScalarField}; +use nova::circuit::{AugmentedCircuit, FCircuit, State}; +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]; + 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 += 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![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![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![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) +} 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/circuit.rs b/nova/src/circuit.rs new file mode 100644 index 0000000..a557352 --- /dev/null +++ b/nova/src/circuit.rs @@ -0,0 +1,455 @@ +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 kzg::commitment::KzgCommitment; +use kzg::types::{BaseField, ScalarField}; +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. +#[derive(Clone, Debug)] +pub struct State { + pub state: BaseField, +} + +/// 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 { + // F function + pub f_circuit: FC, + // i is the step of IVC + pub i: BaseField, + // trivial instance u⊥ + pub trivial_instance: FInstance, + // The initial state z_0 + pub z_0: State, + // The current state + pub z_i: State, + // 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, +} + +#[allow(dead_code)] +impl 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, + h_i: None, + h_i1: None, + phantom_data_t: PhantomData, + } + } + pub fn run( + &mut self, + u_i: &FInstance, + big_u_i: Option<&FInstance>, + w_i: &FWitness, + com_t: Option<&KzgCommitment>, + ) -> 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.")); + } + + // 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")); + } + + // get hash_x + 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]; + 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 != ScalarField::one() { + return Err(String::from("Scalar u is wrong")); + } + + // 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, u_i, big_u_i.unwrap(), com_t.unwrap()); + + // compute z_{i+1} = F(z_i, w_i) + 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); + + // store the next hash + self.h_i1 = Some(new_hash); + // store the next state + self.z_i1 = Some(z_i1); + } 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); + + // store the next hash + self.h_i1 = Some(new_hash); + // store the next state + self.z_i1 = Some(z_i1); + } + + // 4. output the hash + 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 += 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 { + 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_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_i.x { + x.serialize_uncompressed(&mut hasher).unwrap(); + } + + let data = hasher.finalize().to_vec(); + BaseField::from_le_bytes_mod_order(&data) + } +} + +#[cfg(test)] +#[allow(dead_code)] +mod test { + use super::*; + use crate::nifs::nifs_verifier::gen_test_values; + use crate::r1cs::create_trivial_pair; + 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]; + 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, + h_i: None, + h_i1: 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, + h_i: Some(u_1_x), + h_i1: 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, + h_i: Some(u_1_x), + h_i1: 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 new file mode 100644 index 0000000..2f96124 --- /dev/null +++ b/nova/src/ivc/ivc_prover.rs @@ -0,0 +1,60 @@ +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 { + /// IVC prover will fold 2 instance-witness pairs into one via NIFS + /// and generate zkSNARK proof for it. + pub fn prove( + &self, + r1cs: &R1CS, + ivc_proof: &IVCProof, + prover_transcript: &mut Transcript, + ) -> (FWitness, FInstance, ZkIVCProof) { + 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, + &ivc_proof.big_w_i, + &ivc_proof.u_i, + &ivc_proof.big_u_i, + &self.scheme, + prover_transcript, + ); + + // 3. Generate zkSNARK proof + let nifs_proof = + NIFS::::prove(r, &big_w_out, &big_u_out, &self.scheme, prover_transcript); + + ( + 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 { + ( + 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, + }, + ) + } + } +} diff --git a/nova/src/ivc/ivc_verifier.rs b/nova/src/ivc/ivc_verifier.rs new file mode 100644 index 0000000..43e379f --- /dev/null +++ b/nova/src/ivc/ivc_verifier.rs @@ -0,0 +1,447 @@ +use crate::circuit::{AugmentedCircuit, FCircuit}; +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 { + /// 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, + ) -> 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() { + 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", + )); + } + + if zk_ivc_proof.folded_u_proof.is_none() { + return Err(String::from("Verify failed: folding proof must exist")); + } + 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); + 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 + + NIFS::::verify( + &folded_u_proof, + &u_i, + &big_u_i, + &big_u_out, + &com_t, + &self.scheme, + verifier_transcript, + ) + } + } +} + +#[cfg(test)] +#[allow(dead_code)] +mod tests { + use super::*; + use crate::circuit::State; + use crate::ivc::IVCProof; + use crate::nifs::nifs_verifier::gen_test_values; + use crate::r1cs::{create_trivial_pair, FInstance, FWitness}; + use crate::transcript::Transcript; + 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]; + 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() { + // 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 += 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 + + // 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(), + ); + + // 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 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 + 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); + + // 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); + + let mut prover_transcript; + let mut verifier_transcript = Transcript::::default(); + + // create F' + let 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, + h_i: None, + h_i1: None, + phantom_data_t: PhantomData, + }; + + // generate IVC + let mut ivc = IVC:: { + scheme, + augmented_circuit, + }; + + // 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(), + big_w_i: trivial_witness.clone(), + big_u_i: trivial_instance.clone(), + }; + + let mut zk_ivc_proof = ZkIVCProof { + u_i: ivc_proof.u_i, + big_u_i: ivc_proof.big_u_i, + com_t: None, + 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); + + 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 += 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 a trivial instance (via the paper). + // Prover fold u_1 and U_1 into U_2. + + // generate IVC proof. + ivc_proof = IVCProof { + u_i: u_1.clone(), + w_i: w_1, + big_u_i: trivial_instance.clone(), + big_w_i: trivial_witness.clone(), + }; + + // 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, + 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); + } + assert!(res2.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 += 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 + }; + + // Compute W_3, U_3, and zkSNARK proof + (_, _, 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(); + // check if this state is 115. + assert_eq!( + ivc.augmented_circuit.z_i.state, + BaseField::from(115), + "Wrong state" + ); + } +} diff --git a/nova/src/ivc/mod.rs b/nova/src/ivc/mod.rs new file mode 100644 index 0000000..eb6747c --- /dev/null +++ b/nova/src/ivc/mod.rs @@ -0,0 +1,67 @@ +mod ivc_prover; +mod ivc_verifier; + +use crate::circuit::{AugmentedCircuit, FCircuit}; +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 +/// of u and U. +pub struct ZkIVCProof { + pub u_i: FInstance, + pub big_u_i: FInstance, + pub com_t: Option, + 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, + pub big_u_i: FInstance, + pub big_w_i: FWitness, +} + +#[allow(dead_code)] +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(), + } + } + + // Generate a trivial IVC proof. + 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(), + } + } +} + +#[allow(dead_code)] +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, + } + } +} + +/// IVC structure includes a scheme for commitment and an augmented F' function +pub struct IVC { + pub scheme: KzgScheme, + pub augmented_circuit: AugmentedCircuit, +} diff --git a/nova/src/lib.rs b/nova/src/lib.rs new file mode 100644 index 0000000..8b65ecd --- /dev/null +++ b/nova/src/lib.rs @@ -0,0 +1,6 @@ +pub mod circuit; +pub mod ivc; +pub mod nifs; +pub mod r1cs; +pub mod transcript; +pub mod utils; diff --git a/nova/src/nifs/mod.rs b/nova/src/nifs/mod.rs new file mode 100644 index 0000000..75e3553 --- /dev/null +++ b/nova/src/nifs/mod.rs @@ -0,0 +1,108 @@ +use ark_ec::CurveGroup; +use std::marker::PhantomData; +use std::ops::Mul; + +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::opening::KzgOpening; +use kzg::types::ScalarField; +use sha2::Digest; + +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. +#[derive(Clone)] +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 + /// T = AZ1 ◦ BZ2 + AZ2 ◦ BZ1 − u1 · CZ2 − u2 · CZ1. + pub fn compute_t( + r1cs: &R1CS, + u1: ScalarField, + u2: ScalarField, + 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); + 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 + } + + /// Fold two witnesses into one. + /// E ← E1 + r · T + r^2 · E2 + /// W ← W1 + r · W2 + pub fn fold_witness( + r: ScalarField, + fw1: &FWitness, + fw2: &FWitness, + 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_w = fw1.w.iter().zip(&fw2.w).map(|(a, b)| *a + *b * r).collect(); + + FWitness { e: new_e, w: new_w } + } + + /// 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, + 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 + fi2.u * r; + let new_x = fi1.x.iter().zip(&fi2.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, + } + } +} diff --git a/nova/src/nifs/nifs_prover.rs b/nova/src/nifs/nifs_prover.rs new file mode 100644 index 0000000..764dbce --- /dev/null +++ b/nova/src/nifs/nifs_prover.rs @@ -0,0 +1,115 @@ +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 sha2::Digest; + +impl NIFS { + /// Prover output a folded instance-witness pair, com_T and challenge r via Fiat-Shamir + pub fn prover( + r1cs: &R1CS, + fw1: &FWitness, + fw2: &FWitness, + fi1: &FInstance, + fi2: &FInstance, + 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); + + 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) + } + + /// Generate NIFS proof. Create openings by using KZG commitment + 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_w, + } + } +} + +#[cfg(test)] + +mod test { + use super::*; + use crate::nifs::nifs_verifier::gen_test_values; + use crate::r1cs::is_r1cs_satisfied; + 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(), + ); + + // 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 new file mode 100644 index 0000000..03537c1 --- /dev/null +++ b/nova/src/nifs/nifs_verifier.rs @@ -0,0 +1,210 @@ +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 kzg::commitment::KzgCommitment; +use kzg::scheme::KzgScheme; +use kzg::types::ScalarField; +use sha2::Digest; + +impl NIFS { + /// NIFS.V generate the folded instance. + pub fn verifier( + r: ScalarField, + fi1: &FInstance, + fi2: &FInstance, + com_t: &KzgCommitment, + ) -> FInstance { + 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, + fi2: &FInstance, + fi3: &FInstance, // folded instance. + com_t: &KzgCommitment, + scheme: &KzgScheme, + transcript: &mut Transcript, + ) -> Result<(), String> { + // verify challenge. + let mut res = Self::verify_challenge(proof.r, fi1.u, fi2.u, com_t, transcript); + res.as_ref()?; + + // verify opening. + res = Self::verify_opening(proof, fi3, scheme, transcript); + res.as_ref()?; + + Ok(()) + } + + /// Verify challenge r via Fiat-Shamir + pub fn verify_challenge( + r: ScalarField, + fi1_u: ScalarField, + fi2_u: ScalarField, + com_t: &KzgCommitment, + transcript: &mut Transcript, + ) -> Result<(), String> { + // Recreate challenge r + transcript.feed_scalar_num(fi1_u); + transcript.feed_scalar_num(fi2_u); + 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")); + } + + Ok(()) + } + + /// Verify KZG opening + 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", + )); + } + + // Verify opening + 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(()) + } +} + +#[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 ) + + 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![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![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) +} + +#[cfg(test)] +#[allow(dead_code)] +mod tests { + use super::*; + use crate::nifs::{FWitness, NIFS}; + use kzg::srs::Srs; + use sha2::Sha256; + + #[test] + fn test_one_fold() { + // 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 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()); + + 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()); + } +} diff --git a/nova/src/r1cs/mod.rs b/nova/src/r1cs/mod.rs new file mode 100644 index 0000000..4589083 --- /dev/null +++ b/nova/src/r1cs/mod.rs @@ -0,0 +1,169 @@ +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; + +/// 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, Clone)] +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 +#[derive(Debug, Clone)] +pub struct FWitness { + pub e: Vec, + // pub rE: ScalarField, + pub w: Vec, + // pub rW: ScalarField, +} +#[allow(dead_code)] +impl FWitness { + pub fn new(w: &[ScalarField], len: usize) -> Self { + FWitness { + e: vec![ScalarField::zero(); len], + // rE: ScalarField::rand(&mut rand::thread_rng()), + w: w.into(), + // rW: ScalarField::rand(&mut rand::thread_rng()), + } + } + + /// 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], + w: vec![ScalarField::zero(); len], + } + } + + /// Commit a witness into its corresponding instance. + 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); + // cW.0 = cW.0.mul(self.rW).into_affine(); + + FInstance { + com_e, + u: ScalarField::one(), + com_w, + x: x.into(), + } + } +} + +/// This function creates a trivial instance-witness pair +#[allow(dead_code)] +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) +} + +/// 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, + 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 { + 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; + + #[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(), + ); + + // 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 ok = is_r1cs_satisfied(&r1cs, &u[0], &w[0], &scheme); + + if ok.is_err() { + println!("{:?}", ok); + } + assert!(ok.is_ok()); + } +} diff --git a/nova/src/transcript.rs b/nova/src/transcript.rs new file mode 100644 index 0000000..6014387 --- /dev/null +++ b/nova/src/transcript.rs @@ -0,0 +1,214 @@ +use std::marker::PhantomData; + +use ark_bls12_381::Fr; +use ark_ff::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, +} + +#[allow(dead_code)] +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 + } + + /// 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 { + 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_bls12_381::Fr; + use ark_ec::{AffineRepr, CurveGroup}; + use kzg::types::G1Point; + use sha2::Sha256; + + 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(&[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"); + 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..212f905 --- /dev/null +++ b/nova/src/utils.rs @@ -0,0 +1,163 @@ +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: &[F]) -> Vec { + let mut r: Vec = vec![F::zero(); matrix.len()]; + for i in 0..matrix.len() { + for (j, z_j) in z.iter().enumerate().take(matrix[i].len()) { + r[i] += matrix[i][j] * z_j; + } + } + 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: &[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]; + } + 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: &[F], u: F) -> Vec { + let mut r: Vec = vec![F::zero(); a.len()]; + for i in 0..a.len() { + r[i] = a[i] * u; + } + 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: &[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() { + r[i] = a[i] - b[i]; + } + 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: &[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() { + r[i] = a[i] + b[i]; + } + 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: &[F], b: &[F]) -> bool { + if a.len() != b.len() { + return false; + } + + for i in 0..a.len() { + if a[i] != b[i] { + return false; + } + } + 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()]; + 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 +} + +/// 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()]; + for i in 0..z.len() { + r[i] = F::from(z[i] as u64); + } + r +}