saffron/
read_proof.rs

1//! This module defines the read proof prover and verifier. Given a
2//! query vector q, a vector of data d, and a commitment to this data
3//! C, the prover will return an answer a and a proof that the answers
4//! correspond to the data committed in C at the specified indexes in
5//! the query.
6//!
7//! The folding version is TBD
8//! We call data the data vector that is stored and queried
9//! We call answer the vector such that `answer[i] = data[i] * query[i]`
10
11use 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// #[serde_as]
29#[derive(Debug, Clone)]
30// TODO? serialize, deserialize
31pub struct ReadProof {
32    // Commitment to the query vector
33    pub query_comm: Curve,
34    // Commitment to the answer
35    pub answer_comm: Curve,
36    // Commitment of quotient polynomial T (aka t_comm)
37    pub quotient_comm: Curve,
38
39    // Evaluation of data polynomial at the required challenge point
40    pub data_eval: ScalarField,
41    // Evaluation of query polynomial at the required challenge point
42    pub query_eval: ScalarField,
43    // Evaluation of answer polynomial at the required challenge point
44    pub answer_eval: ScalarField,
45
46    // Polynomial commitment’s proof for the validity of returned evaluations
47    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 is the data that is stored and queried
57    data: &[ScalarField],
58    // data[i] is queried if query[i] ≠ 0
59    query: &[ScalarField],
60    // answer[i] = data[i] * query[i]
61    answer: &[ScalarField],
62    // Commitment to data
63    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    // TODO:
80
81    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    // coefficient form, over d4? d2?
88    // quotient_Poly has degree d1
89    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        // q×d - a
95        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        // We compute the polynomial t(X) by dividing the constraints polynomial
104        // by the vanishing polynomial, i.e. Z_H(X).
105        let (quotient, res) = numerator_eval_interpolated.divide_by_vanishing_poly(domain.d1);
106        // As the constraints must be verified on H, the rest of the division
107        // must be equal to 0 as the constraints polynomial and Z_H(X) are both
108        // equal on H.
109        if !res.is_zero() {
110            fail_final_q_division();
111        }
112
113        quotient
114    };
115
116    // commit to the quotient polynomial $t$.
117    // num_chunks = 1 because our constraint is degree 2, which makes the quotient polynomial of degree d1
118    let quotient_comm = srs.commit_non_hiding(&quotient_poly, 1).chunks[0];
119    fq_sponge.absorb_g(&[quotient_comm]);
120
121    // aka zeta
122    let evaluation_point = fq_sponge.challenge();
123
124    // Fiat Shamir - absorbing evaluations
125    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    // Generate scalars used as combiners for sub-statements within our IPA opening proof.
139    let polyscale = fr_sponge.challenge().to_field(endo_r);
140    let evalscale = fr_sponge.challenge().to_field(endo_r);
141
142    // Creating the polynomials for the batch proof
143    // Gathering all polynomials to use in the opening proof
144    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(&quotient_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    // Commitment to data
186    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    // Generate scalars used as combiners for sub-statements within our IPA opening proof.
222    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}