Skip to content

Commit

Permalink
SOT-155: Nova implementation
Browse files Browse the repository at this point in the history
* Add ScalarField def and vector commitment feature.

* Create Nova Folding Scheme lib

* Create Fiat-Shamir Transcript and Matrix operator functions

* Add Folding Scheme implementation.

* Add satisfaction condition for R1CS

* Separate the NIFS verification process into 2 different functions

* Write a simple version of AugmentedCircuit

* Reformat NIFS code & Add ZkIVCProof to AugmentedCircuit function

* Implement the 'prove' function for IVC.

* Implement the 'verify' function for IVC.

* Change the parameters of circuit.

* Write a simple testcase for IVC

* Rewrite the simple test case into a step-by-step IVC test case

* Write an iterable usage test case for IVC

* Refine code and add more test cases.

* refactor: Refine code and add comments

* feat: Add examples for Nova lib & Refine code.

* refactor: Refine code
  • Loading branch information
VanhGer authored Dec 1, 2024
1 parent 50dbb25 commit 768c9c8
Show file tree
Hide file tree
Showing 17 changed files with 2,277 additions and 2 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
40 changes: 39 additions & 1 deletion kzg/src/scheme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<Fr>) -> 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(&quotient_poly);
KzgOpening(opening, evaluation_at_z)
}

/// Verifies the correctness of an opening.
///
/// # Parameters
Expand Down
2 changes: 2 additions & 0 deletions kzg/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ use ark_poly::univariate::DensePolynomial;

pub type G1Point = <Bls12<ark_bls12_381::Config> as Pairing>::G1Affine;
pub type G2Point = <Bls12<ark_bls12_381::Config> as Pairing>::G2Affine;
pub type ScalarField = <Bls12<ark_bls12_381::Config> as Pairing>::ScalarField;
pub type BaseField = <Bls12<ark_bls12_381::Config> as Pairing>::BaseField;
pub type Poly = DensePolynomial<Fr>;
20 changes: 20 additions & 0 deletions nova/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"}
199 changes: 199 additions & 0 deletions nova/examples/examples.rs
Original file line number Diff line number Diff line change
@@ -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::<ScalarField>(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<FWitness> = witnesses
.iter()
.map(|witness| FWitness::new(witness, matrix_a.len()))
.collect();
let mut u: Vec<FInstance> = 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::<Sha256>::default();

// create F'
let augmented_circuit =
AugmentedCircuit::<Sha256, TestCircuit>::new(f_circuit, &trivial_instance, &z[0]);

// generate IVC
let mut ivc = IVC::<Sha256, TestCircuit> {
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::<Sha256>::default();
verifier_transcript = Transcript::<Sha256>::default();

let hash_x = AugmentedCircuit::<Sha256, TestCircuit>::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<F: PrimeField>(inputs: Vec<usize>) -> (R1CS<F>, Vec<Vec<F>>, Vec<Vec<F>>) {
// 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::<F>(&[
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::<F>(&[
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::<F>(&[
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<F>> = Vec::new();
let mut x: Vec<Vec<F>> = Vec::new();
for input in inputs {
let w_i = to_f_vec::<F>(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::<F>(vec![input * input * input + input + 5]); // output: x^3 + x + 5
x.push(x_i.clone());
}

let r1cs = R1CS::<F> {
matrix_a: a,
matrix_b: b,
matrix_c: c,
num_io: 1,
num_vars: 4,
};
(r1cs, w, x)
}
2 changes: 2 additions & 0 deletions nova/nova.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
This is a simple implementation of [NOVA](https://eprint.iacr.org/2021/370.pdf).

Loading

0 comments on commit 768c9c8

Please sign in to comment.