Skip to content

Commit 768c9c8

Browse files
authored
SOT-155: Nova implementation
* 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
1 parent 50dbb25 commit 768c9c8

File tree

17 files changed

+2277
-2
lines changed

17 files changed

+2277
-2
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[workspace]
2-
members = ["plonk", "kzg", "fri"]
2+
members = ["plonk", "kzg", "fri", "nova"]
33
resolver = "2"
44

55
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

kzg/src/scheme.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ use ark_ec::pairing::Pairing;
66
use ark_ec::short_weierstrass::Affine;
77
use ark_ec::{AffineRepr, CurveGroup};
88
use ark_ff::{One, Zero};
9+
use ark_poly::univariate::DensePolynomial;
910
use ark_poly::{DenseUVPolynomial, Polynomial};
1011
use rand::{Rng, RngCore};
1112

1213
use crate::commitment::KzgCommitment;
1314
use crate::opening::KzgOpening;
1415
use crate::srs::Srs;
15-
use crate::types::{G1Point, Poly};
16+
use crate::types::{G1Point, Poly, ScalarField};
1617

1718
/// Implements the KZG polynomial commitment scheme.
1819
///
@@ -50,6 +51,21 @@ impl KzgScheme {
5051
KzgCommitment(commitment)
5152
}
5253

54+
/// Commits to a coefficient vector using the KZG scheme.
55+
///
56+
/// # Parameters
57+
///
58+
/// - `coeffs`: The coefficient vector to be committed to.
59+
///
60+
/// # Returns
61+
///
62+
/// The commitment to the polynomial.
63+
pub fn commit_vector(&self, coeffs: &[ScalarField]) -> KzgCommitment {
64+
let new_poly = DensePolynomial::from_coefficients_vec(coeffs.into());
65+
let commitment = self.evaluate_in_s(&new_poly);
66+
KzgCommitment(commitment)
67+
}
68+
5369
/// Commits to a parameter using the KZG scheme.
5470
///
5571
/// # Parameters
@@ -103,6 +119,28 @@ impl KzgScheme {
103119
KzgOpening(opening, evaluation_at_z)
104120
}
105121

122+
/// Opens a commitment at a specified point.
123+
///
124+
/// # Parameters
125+
///
126+
/// - `coeffs`: The coefficient vector to be opened.
127+
/// - `z`: The point at which the polynomial is opened.
128+
///
129+
/// # Returns
130+
///
131+
/// The opening at the specified point.
132+
pub fn open_vector(&self, coeffs: &[ScalarField], z: impl Into<Fr>) -> KzgOpening {
133+
let z = z.into();
134+
let mut polynomial = DensePolynomial::from_coefficients_vec(coeffs.into());
135+
let evaluation_at_z = polynomial.evaluate(&z);
136+
let first = polynomial.coeffs.first_mut().expect("at least 1");
137+
*first -= evaluation_at_z;
138+
let root = Poly::from_coefficients_slice(&[-z, 1.into()]);
139+
let quotient_poly = &polynomial / &root;
140+
let opening = self.evaluate_in_s(&quotient_poly);
141+
KzgOpening(opening, evaluation_at_z)
142+
}
143+
106144
/// Verifies the correctness of an opening.
107145
///
108146
/// # Parameters

kzg/src/types.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@ use ark_poly::univariate::DensePolynomial;
55

66
pub type G1Point = <Bls12<ark_bls12_381::Config> as Pairing>::G1Affine;
77
pub type G2Point = <Bls12<ark_bls12_381::Config> as Pairing>::G2Affine;
8+
pub type ScalarField = <Bls12<ark_bls12_381::Config> as Pairing>::ScalarField;
9+
pub type BaseField = <Bls12<ark_bls12_381::Config> as Pairing>::BaseField;
810
pub type Poly = DensePolynomial<Fr>;

nova/Cargo.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[package]
2+
name = "nova"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
7+
[[example]]
8+
name = "nova-example"
9+
path = "examples/examples.rs"
10+
11+
[dependencies]
12+
ark-ff = "0.4.2"
13+
ark-ec = "0.4.2"
14+
ark-bls12-381 = "0.4.0"
15+
ark-serialize = "0.4.2"
16+
ark-std = "0.4.0"
17+
rand = "0.8.5"
18+
sha2 = "0.10"
19+
kzg = { path = "../kzg" }
20+
plonk = {path = "../plonk"}

nova/examples/examples.rs

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
use ark_ff::{BigInteger, One, PrimeField, Zero};
2+
use kzg::scheme::KzgScheme;
3+
use kzg::srs::Srs;
4+
use kzg::types::{BaseField, ScalarField};
5+
use nova::circuit::{AugmentedCircuit, FCircuit, State};
6+
use nova::ivc::{IVCProof, ZkIVCProof, IVC};
7+
use nova::r1cs::{create_trivial_pair, FInstance, FWitness, R1CS};
8+
use nova::transcript::Transcript;
9+
use nova::utils::{to_f_matrix, to_f_vec};
10+
use sha2::Sha256;
11+
12+
struct TestCircuit {}
13+
impl FCircuit for TestCircuit {
14+
fn run(&self, z_i: &State, w_i: &FWitness) -> State {
15+
let x = w_i.w[0];
16+
let res = x * x * x + x + ScalarField::from(5);
17+
let base_res = BaseField::from_le_bytes_mod_order(&res.into_bigint().to_bytes_le());
18+
19+
State {
20+
state: z_i.state + base_res,
21+
}
22+
}
23+
}
24+
fn main() {
25+
// (x0^3 + x0 + 5) + (x1^3 + x1 + 5) + (x2^3 + x2 + 5) + (x3^3 + x2 + 5) = 130
26+
// x0 = 3, x1 = 4, x2 = 1, x3 = 2
27+
28+
// generate R1CS, witnesses and public input, output.
29+
let (r1cs, witnesses, x) = gen_test_values::<ScalarField>(vec![3, 4, 1, 2]);
30+
let (matrix_a, _, _) = (
31+
r1cs.matrix_a.clone(),
32+
r1cs.matrix_b.clone(),
33+
r1cs.matrix_c.clone(),
34+
);
35+
36+
// Trusted setup
37+
let domain_size = witnesses[0].len() + x[0].len() + 1;
38+
let srs = Srs::new(domain_size);
39+
let scheme = KzgScheme::new(srs);
40+
let x_len = x[0].len();
41+
42+
// Generate witnesses and instances
43+
let w: Vec<FWitness> = witnesses
44+
.iter()
45+
.map(|witness| FWitness::new(witness, matrix_a.len()))
46+
.collect();
47+
let mut u: Vec<FInstance> = w
48+
.iter()
49+
.zip(x)
50+
.map(|(w, x)| w.commit(&scheme, &x))
51+
.collect();
52+
53+
// step i
54+
let mut i = BaseField::zero();
55+
56+
// generate trivial instance-witness pair
57+
let (trivial_witness, trivial_instance) =
58+
create_trivial_pair(x_len, witnesses[0].len(), &scheme);
59+
60+
// generate f_circuit instance
61+
let f_circuit = TestCircuit {};
62+
63+
// generate states
64+
let mut z = vec![
65+
State {
66+
state: BaseField::from(0)
67+
};
68+
5
69+
];
70+
for index in 1..5 {
71+
z[index] = f_circuit.run(&z[index - 1], &w[index - 1]);
72+
}
73+
74+
let mut prover_transcript;
75+
let mut verifier_transcript = Transcript::<Sha256>::default();
76+
77+
// create F'
78+
let augmented_circuit =
79+
AugmentedCircuit::<Sha256, TestCircuit>::new(f_circuit, &trivial_instance, &z[0]);
80+
81+
// generate IVC
82+
let mut ivc = IVC::<Sha256, TestCircuit> {
83+
scheme,
84+
augmented_circuit,
85+
};
86+
87+
// initialize IVC proof, zkIVCProof, folded witness and folded instance
88+
let mut ivc_proof = IVCProof::trivial_ivc_proof(&trivial_instance, &trivial_witness);
89+
let mut zk_ivc_proof = ZkIVCProof::trivial_zk_ivc_proof(&trivial_instance);
90+
let mut folded_witness = trivial_witness.clone();
91+
let mut folded_instance = trivial_instance.clone();
92+
93+
let mut res;
94+
for step in 0..4 {
95+
println!("Step: {:?}", step);
96+
if step == 0 {
97+
res = ivc.augmented_circuit.run(&u[step], None, &w[step], None);
98+
} else {
99+
res = ivc.augmented_circuit.run(
100+
&ivc_proof.u_i,
101+
Some(&ivc_proof.big_u_i.clone()),
102+
&ivc_proof.w_i,
103+
Some(&zk_ivc_proof.com_t.clone().unwrap()),
104+
);
105+
}
106+
107+
if res.is_err() {
108+
println!("{:?}", res);
109+
}
110+
assert!(res.is_ok());
111+
112+
// verifier verify this step
113+
let verify = ivc.verify(&zk_ivc_proof, &mut verifier_transcript);
114+
if verify.is_err() {
115+
println!("{:?}", verify);
116+
}
117+
assert!(verify.is_ok());
118+
119+
// update for next step
120+
121+
if step != 3 {
122+
// do not update if we have done with IVC
123+
ivc.augmented_circuit.next_step();
124+
i += BaseField::one();
125+
assert_eq!(ivc.augmented_circuit.z_i.state, z[step + 1].state);
126+
prover_transcript = Transcript::<Sha256>::default();
127+
verifier_transcript = Transcript::<Sha256>::default();
128+
129+
let hash_x = AugmentedCircuit::<Sha256, TestCircuit>::hash_io(
130+
i,
131+
&z[0],
132+
&z[step + 1],
133+
&folded_instance,
134+
);
135+
// convert u_1_x from BaseField into ScalarField
136+
u[step + 1].x = vec![ScalarField::from_le_bytes_mod_order(
137+
&hash_x.into_bigint().to_bytes_le(),
138+
)];
139+
140+
// generate ivc_proof and zkSNARK proof.
141+
ivc_proof = IVCProof::new(
142+
&u[step + 1],
143+
&w[step + 1],
144+
&folded_instance,
145+
&folded_witness,
146+
);
147+
(folded_witness, folded_instance, zk_ivc_proof) =
148+
ivc.prove(&r1cs, &ivc_proof, &mut prover_transcript);
149+
}
150+
}
151+
}
152+
153+
fn gen_test_values<F: PrimeField>(inputs: Vec<usize>) -> (R1CS<F>, Vec<Vec<F>>, Vec<Vec<F>>) {
154+
// R1CS for: x^3 + x + 5 = y (example from article
155+
// https://vitalik.eth.limo/general/2016/12/10/qap.html )
156+
157+
let a = to_f_matrix::<F>(&[
158+
vec![1, 0, 0, 0, 0, 0],
159+
vec![0, 1, 0, 0, 0, 0],
160+
vec![1, 0, 1, 0, 0, 0],
161+
vec![0, 0, 0, 1, 0, 5],
162+
]);
163+
let b = to_f_matrix::<F>(&[
164+
vec![1, 0, 0, 0, 0, 0],
165+
vec![1, 0, 0, 0, 0, 0],
166+
vec![0, 0, 0, 0, 0, 1],
167+
vec![0, 0, 0, 0, 0, 1],
168+
]);
169+
let c = to_f_matrix::<F>(&[
170+
vec![0, 1, 0, 0, 0, 0],
171+
vec![0, 0, 1, 0, 0, 0],
172+
vec![0, 0, 0, 1, 0, 0],
173+
vec![0, 0, 0, 0, 1, 0],
174+
]);
175+
176+
// generate n witnesses
177+
let mut w: Vec<Vec<F>> = Vec::new();
178+
let mut x: Vec<Vec<F>> = Vec::new();
179+
for input in inputs {
180+
let w_i = to_f_vec::<F>(vec![
181+
input,
182+
input * input, // x^2
183+
input * input * input, // x^2 * x
184+
input * input * input + input, // x^3 + x
185+
]);
186+
w.push(w_i.clone());
187+
let x_i = to_f_vec::<F>(vec![input * input * input + input + 5]); // output: x^3 + x + 5
188+
x.push(x_i.clone());
189+
}
190+
191+
let r1cs = R1CS::<F> {
192+
matrix_a: a,
193+
matrix_b: b,
194+
matrix_c: c,
195+
num_io: 1,
196+
num_vars: 4,
197+
};
198+
(r1cs, w, x)
199+
}

nova/nova.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
This is a simple implementation of [NOVA](https://eprint.iacr.org/2021/370.pdf).
2+

0 commit comments

Comments
 (0)