sonobe/src/folding/nova/nifs.rs
src/folding/nova/nifs.rsRaw
1use ark_crypto_primitives::sponge::Absorb;
2use ark_ec::{CurveGroup, Group};
3use ark_std::One;
4use std::marker::PhantomData;
5
6use crate::ccs::r1cs::R1CS;
7use crate::folding::nova::{CommittedInstance, Witness};
8use crate::pedersen::{Params as PedersenParams, Pedersen, Proof as PedersenProof};
9use crate::transcript::Transcript;
10use crate::utils::vec::*;
11use crate::Error;
12
13/// Implements the Non-Interactive Folding Scheme described in section 4 of
14/// https://eprint.iacr.org/2021/370.pdf
15pub struct NIFS<C: CurveGroup> {
16 _phantom: PhantomData<C>,
17}
18
19impl<C: CurveGroup> NIFS<C>
20where
21 <C as Group>::ScalarField: Absorb,
22{
23 // compute_T: compute cross-terms T
24 pub fn compute_T(
25 r1cs: &R1CS<C::ScalarField>,
26 u1: C::ScalarField,
27 u2: C::ScalarField,
28 z1: &[C::ScalarField],
29 z2: &[C::ScalarField],
30 ) -> Result<Vec<C::ScalarField>, Error> {
31 let (A, B, C) = (r1cs.A.clone(), r1cs.B.clone(), r1cs.C.clone());
32
33 // this is parallelizable (for the future)
34 let Az1 = mat_vec_mul_sparse(&A, z1);
35 let Bz1 = mat_vec_mul_sparse(&B, z1);
36 let Cz1 = mat_vec_mul_sparse(&C, z1);
37 let Az2 = mat_vec_mul_sparse(&A, z2);
38 let Bz2 = mat_vec_mul_sparse(&B, z2);
39 let Cz2 = mat_vec_mul_sparse(&C, z2);
40
41 let Az1_Bz2 = hadamard(&Az1, &Bz2)?;
42 let Az2_Bz1 = hadamard(&Az2, &Bz1)?;
43 let u1Cz2 = vec_scalar_mul(&Cz2, &u1);
44 let u2Cz1 = vec_scalar_mul(&Cz1, &u2);
45
46 vec_sub(&vec_sub(&vec_add(&Az1_Bz2, &Az2_Bz1)?, &u1Cz2)?, &u2Cz1)
47 }
48
49 pub fn fold_witness(
50 r: C::ScalarField,
51 w1: &Witness<C>,
52 w2: &Witness<C>,
53 T: &[C::ScalarField],
54 rT: C::ScalarField,
55 ) -> Result<Witness<C>, Error> {
56 let r2 = r * r;
57 let E: Vec<C::ScalarField> = vec_add(
58 &vec_add(&w1.E, &vec_scalar_mul(T, &r))?,
59 &vec_scalar_mul(&w2.E, &r2),
60 )?;
61 let rE = w1.rE + r * rT + r2 * w2.rE;
62 let W: Vec<C::ScalarField> = w1.W.iter().zip(&w2.W).map(|(a, b)| *a + (r * b)).collect();
63
64 let rW = w1.rW + r * w2.rW;
65 Ok(Witness::<C> { E, rE, W, rW })
66 }
67
68 pub fn fold_committed_instance(
69 r: C::ScalarField,
70 ci1: &CommittedInstance<C>,
71 ci2: &CommittedInstance<C>,
72 cmT: &C,
73 ) -> CommittedInstance<C> {
74 let r2 = r * r;
75 let cmE = ci1.cmE + cmT.mul(r) + ci2.cmE.mul(r2);
76 let u = ci1.u + r * ci2.u;
77 let cmW = ci1.cmW + ci2.cmW.mul(r);
78 let x = ci1
79 .x
80 .iter()
81 .zip(&ci2.x)
82 .map(|(a, b)| *a + (r * b))
83 .collect::<Vec<C::ScalarField>>();
84
85 CommittedInstance::<C> { cmE, u, cmW, x }
86 }
87
88 // NIFS.P
89 #[allow(clippy::type_complexity)]
90 pub fn prove(
91 pedersen_params: &PedersenParams<C>,
92 // r comes from the transcript, and is a n-bit (N_BITS_CHALLENGE) element
93 r: C::ScalarField,
94 r1cs: &R1CS<C::ScalarField>,
95 w1: &Witness<C>,
96 ci1: &CommittedInstance<C>,
97 w2: &Witness<C>,
98 ci2: &CommittedInstance<C>,
99 ) -> Result<(Witness<C>, CommittedInstance<C>, Vec<C::ScalarField>, C), Error> {
100 let z1: Vec<C::ScalarField> = [vec![ci1.u], ci1.x.to_vec(), w1.W.to_vec()].concat();
101 let z2: Vec<C::ScalarField> = [vec![ci2.u], ci2.x.to_vec(), w2.W.to_vec()].concat();
102
103 // compute cross terms
104 let T = Self::compute_T(r1cs, ci1.u, ci2.u, &z1, &z2)?;
105 let rT = C::ScalarField::one(); // use 1 as rT since we don't need hiding property for cm(T)
106 let cmT = Pedersen::commit(pedersen_params, &T, &rT);
107
108 // fold witness
109 let w3 = NIFS::<C>::fold_witness(r, w1, w2, &T, rT)?;
110
111 // fold committed instancs
112 let ci3 = NIFS::<C>::fold_committed_instance(r, ci1, ci2, &cmT);
113
114 Ok((w3, ci3, T, cmT))
115 }
116
117 // NIFS.V
118 pub fn verify(
119 // r comes from the transcript, and is a n-bit (N_BITS_CHALLENGE) element
120 r: C::ScalarField,
121 ci1: &CommittedInstance<C>,
122 ci2: &CommittedInstance<C>,
123 cmT: &C,
124 ) -> CommittedInstance<C> {
125 NIFS::<C>::fold_committed_instance(r, ci1, ci2, cmT)
126 }
127
128 /// Verify commited folded instance (ci) relations. Notice that this method does not open the
129 /// commitments, but just checks that the given committed instances (ci1, ci2) when folded
130 /// result in the folded committed instance (ci3) values.
131 pub fn verify_folded_instance(
132 r: C::ScalarField,
133 ci1: &CommittedInstance<C>,
134 ci2: &CommittedInstance<C>,
135 ci3: &CommittedInstance<C>,
136 cmT: &C,
137 ) -> Result<(), Error> {
138 let r2 = r * r;
139 if ci3.cmE != (ci1.cmE + cmT.mul(r) + ci2.cmE.mul(r2))
140 || ci3.u != ci1.u + r * ci2.u
141 || ci3.cmW != (ci1.cmW + ci2.cmW.mul(r))
142 || ci3.x != vec_add(&ci1.x, &vec_scalar_mul(&ci2.x, &r))?
143 {
144 return Err(Error::NotSatisfied);
145 }
146 Ok(())
147 }
148
149 pub fn open_commitments(
150 tr: &mut impl Transcript<C>,
151 pedersen_params: &PedersenParams<C>,
152 w: &Witness<C>,
153 ci: &CommittedInstance<C>,
154 T: Vec<C::ScalarField>,
155 cmT: &C,
156 ) -> Result<[PedersenProof<C>; 3], Error> {
157 let cmE_proof = Pedersen::prove(pedersen_params, tr, &ci.cmE, &w.E, &w.rE)?;
158 let cmW_proof = Pedersen::prove(pedersen_params, tr, &ci.cmW, &w.W, &w.rW)?;
159 let cmT_proof = Pedersen::prove(pedersen_params, tr, cmT, &T, &C::ScalarField::one())?; // cm(T) is committed with rT=1
160 Ok([cmE_proof, cmW_proof, cmT_proof])
161 }
162 pub fn verify_commitments(
163 tr: &mut impl Transcript<C>,
164 pedersen_params: &PedersenParams<C>,
165 ci: CommittedInstance<C>,
166 cmT: C,
167 cm_proofs: [PedersenProof<C>; 3],
168 ) -> Result<(), Error> {
169 if cm_proofs.len() != 3 {
170 // cm_proofs should have length 3: [cmE_proof, cmW_proof, cmT_proof]
171 return Err(Error::NotExpectedLength);
172 }
173 if !Pedersen::verify(pedersen_params, tr, ci.cmE, cm_proofs[0].clone())
174 || !Pedersen::verify(pedersen_params, tr, ci.cmW, cm_proofs[1].clone())
175 || !Pedersen::verify(pedersen_params, tr, cmT, cm_proofs[2].clone())
176 {
177 return Err(Error::CommitmentVerificationFail);
178 }
179 Ok(())
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use crate::ccs::r1cs::tests::{get_test_r1cs, get_test_z};
187 use crate::transcript::poseidon::{tests::poseidon_test_config, PoseidonTranscript};
188 use ark_ff::PrimeField;
189 use ark_pallas::{Fr, Projective};
190 use ark_std::UniformRand;
191
192 pub fn check_relaxed_r1cs<F: PrimeField>(r1cs: &R1CS<F>, z: Vec<F>, u: F, E: &[F]) {
193 let Az = mat_vec_mul_sparse(&r1cs.A, &z);
194 let Bz = mat_vec_mul_sparse(&r1cs.B, &z);
195 let Cz = mat_vec_mul_sparse(&r1cs.C, &z);
196 assert_eq!(hadamard(&Az, &Bz), vec_add(&vec_scalar_mul(&Cz, &u), E));
197 }
198
199 // fold 2 instances into one
200 #[test]
201 fn test_nifs_one_fold() {
202 let r1cs = get_test_r1cs();
203 let z1 = get_test_z(3);
204 let z2 = get_test_z(4);
205 let (w1, x1) = r1cs.split_z(&z1);
206 let (w2, x2) = r1cs.split_z(&z2);
207
208 let w1 = Witness::<Projective>::new(w1.clone(), r1cs.A.n_rows);
209 let w2 = Witness::<Projective>::new(w2.clone(), r1cs.A.n_rows);
210
211 let mut rng = ark_std::test_rng();
212 let pedersen_params = Pedersen::<Projective>::new_params(&mut rng, r1cs.A.n_cols);
213
214 let r = Fr::rand(&mut rng); // folding challenge would come from the transcript
215
216 // compute committed instances
217 let ci1 = w1.commit(&pedersen_params, x1.clone());
218 let ci2 = w2.commit(&pedersen_params, x2.clone());
219
220 // NIFS.P
221 let (w3, _, T, cmT) =
222 NIFS::<Projective>::prove(&pedersen_params, r, &r1cs, &w1, &ci1, &w2, &ci2).unwrap();
223
224 // NIFS.V
225 let ci3 = NIFS::<Projective>::verify(r, &ci1, &ci2, &cmT);
226
227 // naive check that the folded witness satisfies the relaxed r1cs
228 let z3: Vec<Fr> = [vec![ci3.u], ci3.x.to_vec(), w3.W.to_vec()].concat();
229 // check that z3 is as expected
230 let z3_aux = vec_add(&z1, &vec_scalar_mul(&z2, &r)).unwrap();
231 assert_eq!(z3, z3_aux);
232 // check that relations hold for the 2 inputted instances and the folded one
233 check_relaxed_r1cs(&r1cs, z1, ci1.u, &w1.E);
234 check_relaxed_r1cs(&r1cs, z2, ci2.u, &w2.E);
235 check_relaxed_r1cs(&r1cs, z3, ci3.u, &w3.E);
236
237 // check that folded commitments from folded instance (ci) are equal to folding the
238 // use folded rE, rW to commit w3
239 let ci3_expected = w3.commit(&pedersen_params, ci3.x.clone());
240 assert_eq!(ci3_expected.cmE, ci3.cmE);
241 assert_eq!(ci3_expected.cmW, ci3.cmW);
242
243 // NIFS.Verify_Folded_Instance:
244 NIFS::<Projective>::verify_folded_instance(r, &ci1, &ci2, &ci3, &cmT).unwrap();
245
246 let poseidon_config = poseidon_test_config::<Fr>();
247 // init Prover's transcript
248 let mut transcript_p = PoseidonTranscript::<Projective>::new(&poseidon_config);
249 // init Verifier's transcript
250 let mut transcript_v = PoseidonTranscript::<Projective>::new(&poseidon_config);
251
252 // check openings of ci3.cmE, ci3.cmW and cmT
253 let cm_proofs = NIFS::<Projective>::open_commitments(
254 &mut transcript_p,
255 &pedersen_params,
256 &w3,
257 &ci3,
258 T,
259 &cmT,
260 )
261 .unwrap();
262 NIFS::<Projective>::verify_commitments(
263 &mut transcript_v,
264 &pedersen_params,
265 ci3,
266 cmT,
267 cm_proofs,
268 )
269 .unwrap();
270 }
271
272 #[test]
273 fn test_nifs_fold_loop() {
274 let r1cs = get_test_r1cs();
275 let z = get_test_z(3);
276 let (w, x) = r1cs.split_z(&z);
277
278 let mut rng = ark_std::test_rng();
279 let pedersen_params = Pedersen::<Projective>::new_params(&mut rng, r1cs.A.n_cols);
280
281 // prepare the running instance
282 let mut running_instance_w = Witness::<Projective>::new(w.clone(), r1cs.A.n_rows);
283 let mut running_committed_instance = running_instance_w.commit(&pedersen_params, x);
284 check_relaxed_r1cs(
285 &r1cs,
286 z,
287 running_committed_instance.u,
288 &running_instance_w.E,
289 );
290
291 let num_iters = 10;
292 for i in 0..num_iters {
293 // prepare the incomming instance
294 let incomming_instance_z = get_test_z(i + 4);
295 let (w, x) = r1cs.split_z(&incomming_instance_z);
296 let incomming_instance_w = Witness::<Projective>::new(w.clone(), r1cs.A.n_rows);
297 let incomming_committed_instance = incomming_instance_w.commit(&pedersen_params, x);
298 check_relaxed_r1cs(
299 &r1cs,
300 incomming_instance_z.clone(),
301 incomming_committed_instance.u,
302 &incomming_instance_w.E,
303 );
304
305 let r = Fr::rand(&mut rng); // folding challenge would come from the transcript
306
307 // NIFS.P
308 let (folded_w, _, _, cmT) = NIFS::<Projective>::prove(
309 &pedersen_params,
310 r,
311 &r1cs,
312 &running_instance_w,
313 &running_committed_instance,
314 &incomming_instance_w,
315 &incomming_committed_instance,
316 )
317 .unwrap();
318
319 // NIFS.V
320 let folded_committed_instance = NIFS::<Projective>::verify(
321 r,
322 &running_committed_instance,
323 &incomming_committed_instance,
324 &cmT,
325 );
326
327 let folded_z: Vec<Fr> = [
328 vec![folded_committed_instance.u],
329 folded_committed_instance.x.to_vec(),
330 folded_w.W.to_vec(),
331 ]
332 .concat();
333
334 check_relaxed_r1cs(&r1cs, folded_z, folded_committed_instance.u, &folded_w.E);
335
336 // set running_instance for next loop iteration
337 running_instance_w = folded_w;
338 running_committed_instance = folded_committed_instance;
339 }
340 }
341}