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    domain: EvaluationDomains<ScalarField>,
53    srs: &SRS<Curve>,
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
106            .divide_by_vanishing_poly(domain.d1)
107            .unwrap_or_else(fail_final_q_division);
108        // As the constraints must be verified on H, the rest of the division
109        // must be equal to 0 as the constraints polynomial and Z_H(X) are both
110        // equal on H.
111        if !res.is_zero() {
112            fail_final_q_division();
113        }
114
115        quotient
116    };
117
118    // commit to the quotient polynomial $t$.
119    // num_chunks = 1 because our constraint is degree 2, which makes the quotient polynomial of degree d1
120    let quotient_comm = srs.commit_non_hiding(&quotient_poly, 1).chunks[0];
121    fq_sponge.absorb_g(&[quotient_comm]);
122
123    // aka zeta
124    let evaluation_point = fq_sponge.challenge();
125
126    // Fiat Shamir - absorbing evaluations
127    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    // Generate scalars used as combiners for sub-statements within our IPA opening proof.
141    let polyscale = fr_sponge.challenge().to_field(endo_r);
142    let evalscale = fr_sponge.challenge().to_field(endo_r);
143
144    // Creating the polynomials for the batch proof
145    // Gathering all polynomials to use in the opening proof
146    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(&quotient_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    // Commitment to data
188    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    // Generate scalars used as combiners for sub-statements within our IPA opening proof.
224    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}