1use crate::{Curve, CurveFqSponge, CurveFrSponge, ScalarField};
12use ark_ff::{Field, Zero};
13use ark_poly::{
14 univariate::DensePolynomial, EvaluationDomain, Evaluations, Polynomial,
15 Radix2EvaluationDomain as R2D,
16};
17use kimchi::{circuits::domains::EvaluationDomains, curve::KimchiCurve, plonk_sponge::FrSponge};
18use mina_poseidon::FqSponge;
19use poly_commitment::{
20 commitment::{combined_inner_product, BatchEvaluationProof, CommitmentCurve, Evaluation},
21 ipa::{OpeningProof, SRS},
22 utils::DensePolynomialOrEvaluations,
23 PolyComm, SRS as _,
24};
25use rand::{CryptoRng, RngCore};
26use tracing::instrument;
27
28#[derive(Debug, Clone)]
30pub struct ReadProof {
32 pub query_comm: Curve,
34 pub answer_comm: Curve,
36 pub quotient_comm: Curve,
38
39 pub data_eval: ScalarField,
41 pub query_eval: ScalarField,
43 pub answer_eval: ScalarField,
45
46 pub opening_proof: OpeningProof<Curve>,
48}
49
50#[instrument(skip_all, level = "debug")]
51pub fn prove<RNG>(
52 domain: EvaluationDomains<ScalarField>,
53 srs: &SRS<Curve>,
54 group_map: &<Curve as CommitmentCurve>::Map,
55 rng: &mut RNG,
56 data: &[ScalarField],
58 query: &[ScalarField],
60 answer: &[ScalarField],
62 data_comm: &Curve,
64) -> ReadProof
65where
66 RNG: RngCore + CryptoRng,
67{
68 let mut fq_sponge = CurveFqSponge::new(Curve::other_curve_sponge_params());
69
70 let data_d1 = Evaluations::from_vec_and_domain(data.to_vec(), domain.d1);
71 let data_poly: DensePolynomial<ScalarField> = data_d1.interpolate_by_ref();
72 let data_comm: PolyComm<Curve> = PolyComm {
73 chunks: vec![*data_comm],
74 };
75
76 let query_d1 = Evaluations::from_vec_and_domain(query.to_vec(), domain.d1);
77 let query_poly: DensePolynomial<ScalarField> = query_d1.interpolate_by_ref();
78 let query_comm: Curve = srs.commit_non_hiding(&query_poly, 1).chunks[0];
79 let answer_d1 = Evaluations::from_vec_and_domain(answer.to_vec(), domain.d1);
82 let answer_poly: DensePolynomial<ScalarField> = answer_d1.interpolate_by_ref();
83 let answer_comm: Curve = srs.commit_non_hiding(&answer_poly, 1).chunks[0];
84
85 fq_sponge.absorb_g(&[data_comm.chunks[0], query_comm, answer_comm]);
86
87 let quotient_poly: DensePolynomial<ScalarField> = {
90 let data_d2 = data_poly.evaluate_over_domain_by_ref(domain.d2);
91 let query_d2 = query_poly.evaluate_over_domain_by_ref(domain.d2);
92 let answer_d2 = answer_poly.evaluate_over_domain_by_ref(domain.d2);
93
94 let numerator_eval: Evaluations<ScalarField, R2D<ScalarField>> =
96 &(&data_d2 * &query_d2) - &answer_d2;
97
98 let numerator_eval_interpolated = numerator_eval.interpolate();
99
100 let fail_final_q_division = || {
101 panic!("Division by vanishing poly must not fail at this point, we checked it before")
102 };
103 let (quotient, res) = numerator_eval_interpolated
106 .divide_by_vanishing_poly(domain.d1)
107 .unwrap_or_else(fail_final_q_division);
108 if !res.is_zero() {
112 fail_final_q_division();
113 }
114
115 quotient
116 };
117
118 let quotient_comm = srs.commit_non_hiding("ient_poly, 1).chunks[0];
121 fq_sponge.absorb_g(&[quotient_comm]);
122
123 let evaluation_point = fq_sponge.challenge();
125
126 let mut fr_sponge = CurveFrSponge::new(Curve::sponge_params());
128 fr_sponge.absorb(&fq_sponge.clone().digest());
129
130 let data_eval = data_poly.evaluate(&evaluation_point);
131 let query_eval = query_poly.evaluate(&evaluation_point);
132 let answer_eval = answer_poly.evaluate(&evaluation_point);
133 let quotient_eval = quotient_poly.evaluate(&evaluation_point);
134
135 for eval in [data_eval, query_eval, answer_eval, quotient_eval].into_iter() {
136 fr_sponge.absorb(&eval);
137 }
138
139 let (_, endo_r) = Curve::endos();
140 let polyscale = fr_sponge.challenge().to_field(endo_r);
142 let evalscale = fr_sponge.challenge().to_field(endo_r);
143
144 let opening_proof_inputs: Vec<_> = {
147 let coefficients_form =
148 DensePolynomialOrEvaluations::<_, R2D<ScalarField>>::DensePolynomial;
149 let non_hiding = |n_chunks| PolyComm {
150 chunks: vec![ScalarField::zero(); n_chunks],
151 };
152
153 vec![
154 (coefficients_form(&data_poly), non_hiding(1)),
155 (coefficients_form(&query_poly), non_hiding(1)),
156 (coefficients_form(&answer_poly), non_hiding(1)),
157 (coefficients_form("ient_poly), non_hiding(1)),
158 ]
159 };
160
161 let opening_proof = srs.open(
162 group_map,
163 opening_proof_inputs.as_slice(),
164 &[evaluation_point],
165 polyscale,
166 evalscale,
167 fq_sponge,
168 rng,
169 );
170
171 ReadProof {
172 query_comm,
173 answer_comm,
174 quotient_comm,
175 data_eval,
176 query_eval,
177 answer_eval,
178 opening_proof,
179 }
180}
181
182pub fn verify<RNG>(
183 domain: EvaluationDomains<ScalarField>,
184 srs: &SRS<Curve>,
185 group_map: &<Curve as CommitmentCurve>::Map,
186 rng: &mut RNG,
187 data_comm: &Curve,
189 proof: &ReadProof,
190) -> bool
191where
192 RNG: RngCore + CryptoRng,
193{
194 let mut fq_sponge = CurveFqSponge::new(Curve::other_curve_sponge_params());
195 fq_sponge.absorb_g(&[*data_comm, proof.query_comm, proof.answer_comm]);
196 fq_sponge.absorb_g(&[proof.quotient_comm]);
197
198 let evaluation_point = fq_sponge.challenge();
199
200 let mut fr_sponge = CurveFrSponge::new(Curve::sponge_params());
201 fr_sponge.absorb(&fq_sponge.clone().digest());
202
203 let vanishing_poly_at_zeta = domain.d1.vanishing_polynomial().evaluate(&evaluation_point);
204 let quotient_eval = {
205 (proof.data_eval * proof.query_eval - proof.answer_eval)
206 * vanishing_poly_at_zeta
207 .inverse()
208 .unwrap_or_else(|| panic!("Inverse fails only with negligible probability"))
209 };
210
211 for eval in [
212 proof.data_eval,
213 proof.query_eval,
214 proof.answer_eval,
215 quotient_eval,
216 ]
217 .into_iter()
218 {
219 fr_sponge.absorb(&eval);
220 }
221
222 let (_, endo_r) = Curve::endos();
223 let polyscale = fr_sponge.challenge().to_field(endo_r);
225 let evalscale = fr_sponge.challenge().to_field(endo_r);
226
227 let coms_and_evaluations = vec![
228 Evaluation {
229 commitment: PolyComm {
230 chunks: vec![*data_comm],
231 },
232 evaluations: vec![vec![proof.data_eval]],
233 },
234 Evaluation {
235 commitment: PolyComm {
236 chunks: vec![proof.query_comm],
237 },
238 evaluations: vec![vec![proof.query_eval]],
239 },
240 Evaluation {
241 commitment: PolyComm {
242 chunks: vec![proof.answer_comm],
243 },
244 evaluations: vec![vec![proof.answer_eval]],
245 },
246 Evaluation {
247 commitment: PolyComm {
248 chunks: vec![proof.quotient_comm],
249 },
250 evaluations: vec![vec![quotient_eval]],
251 },
252 ];
253 let combined_inner_product = {
254 let evaluations: Vec<_> = coms_and_evaluations
255 .iter()
256 .map(|Evaluation { evaluations, .. }| evaluations.clone())
257 .collect();
258
259 combined_inner_product(&polyscale, &evalscale, evaluations.as_slice())
260 };
261
262 srs.verify(
263 group_map,
264 &mut [BatchEvaluationProof {
265 sponge: fq_sponge,
266 evaluation_points: vec![evaluation_point],
267 polyscale,
268 evalscale,
269 evaluations: coms_and_evaluations,
270 opening: &proof.opening_proof,
271 combined_inner_product,
272 }],
273 rng,
274 )
275}
276
277#[cfg(test)]
278mod tests {
279 use super::{prove, verify, ReadProof};
280 use crate::{env, Curve, ScalarField, SRS_SIZE};
281 use ark_ec::AffineRepr;
282 use ark_ff::{One, UniformRand};
283 use ark_poly::{univariate::DensePolynomial, Evaluations};
284 use kimchi::{circuits::domains::EvaluationDomains, groupmap::GroupMap};
285 use mina_curves::pasta::{Fp, Vesta};
286 use once_cell::sync::Lazy;
287 use poly_commitment::{commitment::CommitmentCurve, ipa::SRS, SRS as _};
288 use proptest::prelude::*;
289
290 static SRS: Lazy<SRS<Vesta>> = Lazy::new(|| {
291 if let Ok(srs) = std::env::var("SRS_FILEPATH") {
292 env::get_srs_from_cache(srs)
293 } else {
294 SRS::create(SRS_SIZE)
295 }
296 });
297
298 static DOMAIN: Lazy<EvaluationDomains<ScalarField>> =
299 Lazy::new(|| EvaluationDomains::<ScalarField>::create(SRS_SIZE).unwrap());
300
301 static GROUP_MAP: Lazy<<Vesta as CommitmentCurve>::Map> =
302 Lazy::new(<Vesta as CommitmentCurve>::Map::setup);
303
304 #[test]
305 fn test_read_proof_completeness_soundness() {
306 let mut rng = o1_utils::tests::make_test_rng(None);
307
308 let data: Vec<ScalarField> = {
309 let mut data = vec![];
310 (0..SRS_SIZE).for_each(|_| data.push(Fp::rand(&mut rng)));
311 data
312 };
313
314 let data_poly: DensePolynomial<ScalarField> =
315 Evaluations::from_vec_and_domain(data.clone(), DOMAIN.d1).interpolate();
316 let data_comm: Curve = SRS.commit_non_hiding(&data_poly, 1).chunks[0];
317
318 let query: Vec<ScalarField> = {
319 let mut query = vec![];
320 (0..SRS_SIZE).for_each(|_| query.push(Fp::from(rand::thread_rng().gen::<f64>() < 0.1)));
321 query
322 };
323
324 let answer: Vec<ScalarField> = data.iter().zip(query.iter()).map(|(d, q)| *d * q).collect();
325
326 let proof = prove(
327 *DOMAIN,
328 &SRS,
329 &GROUP_MAP,
330 &mut rng,
331 data.as_slice(),
332 query.as_slice(),
333 answer.as_slice(),
334 &data_comm,
335 );
336 let res = verify(*DOMAIN, &SRS, &GROUP_MAP, &mut rng, &data_comm, &proof);
337
338 assert!(res, "Completeness: Proof must verify");
339
340 let proof_malformed_1 = ReadProof {
341 answer_comm: Curve::zero(),
342 ..proof.clone()
343 };
344
345 let res_1 = verify(
346 *DOMAIN,
347 &SRS,
348 &GROUP_MAP,
349 &mut rng,
350 &data_comm,
351 &proof_malformed_1,
352 );
353
354 assert!(!res_1, "Soundness: Malformed proof #1 must NOT verify");
355
356 let proof_malformed_2 = ReadProof {
357 query_eval: ScalarField::one(),
358 ..proof.clone()
359 };
360
361 let res_2 = verify(
362 *DOMAIN,
363 &SRS,
364 &GROUP_MAP,
365 &mut rng,
366 &data_comm,
367 &proof_malformed_2,
368 );
369
370 assert!(!res_2, "Soundness: Malformed proof #2 must NOT verify");
371 }
372}