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 srs: &SRS<Curve>,
53 domain: EvaluationDomains<ScalarField>,
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.divide_by_vanishing_poly(domain.d1);
106 if !res.is_zero() {
110 fail_final_q_division();
111 }
112
113 quotient
114 };
115
116 let quotient_comm = srs.commit_non_hiding("ient_poly, 1).chunks[0];
119 fq_sponge.absorb_g(&[quotient_comm]);
120
121 let evaluation_point = fq_sponge.challenge();
123
124 let mut fr_sponge = CurveFrSponge::new(Curve::sponge_params());
126 fr_sponge.absorb(&fq_sponge.clone().digest());
127
128 let data_eval = data_poly.evaluate(&evaluation_point);
129 let query_eval = query_poly.evaluate(&evaluation_point);
130 let answer_eval = answer_poly.evaluate(&evaluation_point);
131 let quotient_eval = quotient_poly.evaluate(&evaluation_point);
132
133 for eval in [data_eval, query_eval, answer_eval, quotient_eval].into_iter() {
134 fr_sponge.absorb(&eval);
135 }
136
137 let (_, endo_r) = Curve::endos();
138 let polyscale = fr_sponge.challenge().to_field(endo_r);
140 let evalscale = fr_sponge.challenge().to_field(endo_r);
141
142 let opening_proof_inputs: Vec<_> = {
145 let coefficients_form =
146 DensePolynomialOrEvaluations::<_, R2D<ScalarField>>::DensePolynomial;
147 let non_hiding = |n_chunks| PolyComm {
148 chunks: vec![ScalarField::zero(); n_chunks],
149 };
150
151 vec![
152 (coefficients_form(&data_poly), non_hiding(1)),
153 (coefficients_form(&query_poly), non_hiding(1)),
154 (coefficients_form(&answer_poly), non_hiding(1)),
155 (coefficients_form("ient_poly), non_hiding(1)),
156 ]
157 };
158
159 let opening_proof = srs.open(
160 group_map,
161 opening_proof_inputs.as_slice(),
162 &[evaluation_point],
163 polyscale,
164 evalscale,
165 fq_sponge,
166 rng,
167 );
168
169 ReadProof {
170 query_comm,
171 answer_comm,
172 quotient_comm,
173 data_eval,
174 query_eval,
175 answer_eval,
176 opening_proof,
177 }
178}
179
180pub fn verify<RNG>(
181 srs: &SRS<Curve>,
182 domain: EvaluationDomains<ScalarField>,
183 group_map: &<Curve as CommitmentCurve>::Map,
184 rng: &mut RNG,
185 data_comm: &Curve,
187 proof: &ReadProof,
188) -> bool
189where
190 RNG: RngCore + CryptoRng,
191{
192 let mut fq_sponge = CurveFqSponge::new(Curve::other_curve_sponge_params());
193 fq_sponge.absorb_g(&[*data_comm, proof.query_comm, proof.answer_comm]);
194 fq_sponge.absorb_g(&[proof.quotient_comm]);
195
196 let evaluation_point = fq_sponge.challenge();
197
198 let mut fr_sponge = CurveFrSponge::new(Curve::sponge_params());
199 fr_sponge.absorb(&fq_sponge.clone().digest());
200
201 let vanishing_poly_at_zeta = domain.d1.vanishing_polynomial().evaluate(&evaluation_point);
202 let quotient_eval = {
203 (proof.data_eval * proof.query_eval - proof.answer_eval)
204 * vanishing_poly_at_zeta
205 .inverse()
206 .unwrap_or_else(|| panic!("Inverse fails only with negligible probability"))
207 };
208
209 for eval in [
210 proof.data_eval,
211 proof.query_eval,
212 proof.answer_eval,
213 quotient_eval,
214 ]
215 .into_iter()
216 {
217 fr_sponge.absorb(&eval);
218 }
219
220 let (_, endo_r) = Curve::endos();
221 let polyscale = fr_sponge.challenge().to_field(endo_r);
223 let evalscale = fr_sponge.challenge().to_field(endo_r);
224
225 let coms_and_evaluations = vec![
226 Evaluation {
227 commitment: PolyComm {
228 chunks: vec![*data_comm],
229 },
230 evaluations: vec![vec![proof.data_eval]],
231 },
232 Evaluation {
233 commitment: PolyComm {
234 chunks: vec![proof.query_comm],
235 },
236 evaluations: vec![vec![proof.query_eval]],
237 },
238 Evaluation {
239 commitment: PolyComm {
240 chunks: vec![proof.answer_comm],
241 },
242 evaluations: vec![vec![proof.answer_eval]],
243 },
244 Evaluation {
245 commitment: PolyComm {
246 chunks: vec![proof.quotient_comm],
247 },
248 evaluations: vec![vec![quotient_eval]],
249 },
250 ];
251 let combined_inner_product = {
252 let evaluations: Vec<_> = coms_and_evaluations
253 .iter()
254 .map(|Evaluation { evaluations, .. }| evaluations.clone())
255 .collect();
256
257 combined_inner_product(&polyscale, &evalscale, evaluations.as_slice())
258 };
259
260 srs.verify(
261 group_map,
262 &mut [BatchEvaluationProof {
263 sponge: fq_sponge,
264 evaluation_points: vec![evaluation_point],
265 polyscale,
266 evalscale,
267 evaluations: coms_and_evaluations,
268 opening: &proof.opening_proof,
269 combined_inner_product,
270 }],
271 rng,
272 )
273}
274
275#[cfg(test)]
276mod tests {
277 use super::{prove, verify, ReadProof};
278 use crate::{Curve, ScalarField, SRS_SIZE};
279 use ark_ec::AffineRepr;
280 use ark_ff::{One, UniformRand};
281 use ark_poly::{univariate::DensePolynomial, Evaluations};
282 use kimchi::{circuits::domains::EvaluationDomains, groupmap::GroupMap};
283 use mina_curves::pasta::{Fp, Vesta};
284 use poly_commitment::{commitment::CommitmentCurve, SRS as _};
285 use proptest::prelude::*;
286
287 #[test]
288 fn test_read_proof_completeness_soundness() {
289 let mut rng = o1_utils::tests::make_test_rng(None);
290
291 let srs = poly_commitment::precomputed_srs::get_srs_test();
292 let group_map = <Vesta as CommitmentCurve>::Map::setup();
293 let domain: EvaluationDomains<ScalarField> = EvaluationDomains::create(srs.size()).unwrap();
294
295 let data: Vec<ScalarField> = {
296 let mut data = vec![];
297 (0..SRS_SIZE).for_each(|_| data.push(Fp::rand(&mut rng)));
298 data
299 };
300
301 let data_poly: DensePolynomial<ScalarField> =
302 Evaluations::from_vec_and_domain(data.clone(), domain.d1).interpolate();
303 let data_comm: Curve = srs.commit_non_hiding(&data_poly, 1).chunks[0];
304
305 let query: Vec<ScalarField> = {
306 let mut query = vec![];
307 (0..SRS_SIZE).for_each(|_| query.push(Fp::from(rand::thread_rng().gen::<f64>() < 0.1)));
308 query
309 };
310
311 let answer: Vec<ScalarField> = data.iter().zip(query.iter()).map(|(d, q)| *d * q).collect();
312
313 let proof = prove(
314 &srs,
315 domain,
316 &group_map,
317 &mut rng,
318 data.as_slice(),
319 query.as_slice(),
320 answer.as_slice(),
321 &data_comm,
322 );
323 let res = verify(&srs, domain, &group_map, &mut rng, &data_comm, &proof);
324
325 assert!(res, "Completeness: Proof must verify");
326
327 let proof_malformed_1 = ReadProof {
328 answer_comm: Curve::zero(),
329 ..proof.clone()
330 };
331
332 let res_1 = verify(
333 &srs,
334 domain,
335 &group_map,
336 &mut rng,
337 &data_comm,
338 &proof_malformed_1,
339 );
340
341 assert!(!res_1, "Soundness: Malformed proof #1 must NOT verify");
342
343 let proof_malformed_2 = ReadProof {
344 query_eval: ScalarField::one(),
345 ..proof.clone()
346 };
347
348 let res_2 = verify(
349 &srs,
350 domain,
351 &group_map,
352 &mut rng,
353 &data_comm,
354 &proof_malformed_2,
355 );
356
357 assert!(!res_2, "Soundness: Malformed proof #2 must NOT verify");
358 }
359}