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//!
11//! The considered protocol involves a user, the chain and the storage provider
12//! and behaves as following:
13//! 1. The user sends a request to the chain, containing a commitment to a data
14//!    handled by the storage provider with a query that specifies which indexes
15//!    to read.
16//! 2. The chains includes the query if it’s valid and computes the commitment
17//!    to the query.
18//! 3. The state replicator fetch the request with the data & query commitments
19//!    on the chain, computes the corresponding answer & proof, and sends it to
20//!    the chain.
21//! 4. The chain includes the proof if it verifies, and if it’s consistent with
22//!    the provided answer.
23
24use crate::{
25    commitment::*,
26    storage::Data,
27    utils::{evals_to_polynomial, evals_to_polynomial_and_commitment},
28    Curve, CurveScalarSponge, CurveSponge, ScalarField, Sponge,
29};
30use ark_ff::{Field, One, Zero};
31use ark_poly::{
32    univariate::DensePolynomial, EvaluationDomain, Evaluations, Polynomial,
33    Radix2EvaluationDomain as R2D,
34};
35use kimchi::{circuits::domains::EvaluationDomains, curve::KimchiCurve, plonk_sponge::FrSponge};
36use poly_commitment::{
37    commitment::{combined_inner_product, BatchEvaluationProof, CommitmentCurve, Evaluation},
38    ipa::{OpeningProof, SRS},
39    utils::DensePolynomialOrEvaluations,
40    PolyComm,
41};
42use rand::{CryptoRng, Rng, RngCore};
43use tracing::instrument;
44
45/// Indexes of the data to be read ; this will be stored onchain
46/// Note: indexes are represented with u16, matching indexes from 0 to 2¹⁶ - 1. If the SRS is made bigger, the integer type has to handle this
47pub struct Query {
48    pub query: Vec<u16>,
49}
50
51/// Answer to a query regarding some data
52pub struct Answer {
53    answer: Vec<ScalarField>,
54}
55
56impl Query {
57    fn to_evals_vector(&self, domain_size: usize) -> Vec<ScalarField> {
58        let mut evals = vec![ScalarField::zero(); domain_size];
59        for i in self.query.iter() {
60            evals[*i as usize] = ScalarField::one();
61        }
62        evals
63    }
64    pub fn to_polynomial(&self, domain: R2D<ScalarField>) -> DensePolynomial<ScalarField> {
65        evals_to_polynomial(self.to_evals_vector(domain.size as usize), domain)
66    }
67    /// Computes the commitment to the query from its sparse form, without
68    /// recomputing the polynomial
69    pub fn to_commitment(&self, srs: &SRS<Curve>) -> Curve {
70        let query_evals: Vec<ScalarField> = self.query.iter().map(|_| ScalarField::one()).collect();
71        let indexes: Vec<u64> = self.query.iter().map(|i| *i as u64).collect();
72        commit_sparse(srs, &query_evals, &indexes)
73    }
74    pub fn to_answer(&self, data: &Data<ScalarField>) -> Answer {
75        Answer {
76            answer: self.query.iter().map(|i| data.data[*i as usize]).collect(),
77        }
78    }
79    fn to_answer_evals(&self, data: &[ScalarField], domain_size: usize) -> Vec<ScalarField> {
80        let mut evals = vec![ScalarField::zero(); domain_size];
81        for i in self.query.iter() {
82            evals[*i as usize] = data[*i as usize];
83        }
84        evals
85    }
86    /// Generates a random query, the proportion of indexes queried are defined
87    /// by frequency
88    pub fn random(frequency: f64, srs_size: usize) -> Query {
89        let mut query = vec![];
90        (0..srs_size).for_each(|i| {
91            if rand::thread_rng().gen::<f64>() < frequency {
92                query.push(i as u16)
93            }
94        });
95        Query { query }
96    }
97}
98
99impl Answer {
100    fn to_commitment(&self, query: &Query, srs: &SRS<Curve>) -> Curve {
101        let indexes: Vec<u64> = query.query.iter().map(|i| *i as u64).collect();
102        commit_sparse(srs, &self.answer, &indexes)
103    }
104}
105
106// #[serde_as]
107#[derive(Debug, Clone)]
108// TODO? serialize, deserialize
109pub struct ReadProof {
110    // Commitment to the answer
111    pub answer_comm: Curve,
112    // Commitment of quotient polynomial T (aka t_comm)
113    pub quotient_comm: Curve,
114
115    // Evaluation of data polynomial at the required challenge point
116    pub data_eval: ScalarField,
117    // Evaluation of query polynomial at the required challenge point
118    pub query_eval: ScalarField,
119    // Evaluation of answer polynomial at the required challenge point
120    pub answer_eval: ScalarField,
121
122    // Polynomial commitment’s proof for the validity of returned evaluations
123    pub opening_proof: OpeningProof<Curve>,
124}
125
126#[instrument(skip_all, level = "debug")]
127pub fn prove<RNG>(
128    srs: &SRS<Curve>,
129    domain: EvaluationDomains<ScalarField>,
130    group_map: &<Curve as CommitmentCurve>::Map,
131    rng: &mut RNG,
132    // data is the data that is stored and queried
133    data: &Data<ScalarField>,
134    // data[i] is queried if query[i] ≠ 0
135    query: &Query,
136    // Commitment to data
137    data_comm: &Commitment<Curve>,
138    // Commitment to query
139    query_comm: &Curve,
140) -> ReadProof
141where
142    RNG: RngCore + CryptoRng,
143{
144    let data = &data.data;
145
146    let mut curve_sponge = CurveSponge::new(Curve::other_curve_sponge_params());
147
148    let data_poly = evals_to_polynomial(data.to_vec(), domain.d1);
149
150    let query_poly = query.to_polynomial(domain.d1);
151
152    let (answer_poly, answer_comm) = {
153        let answer = query.to_answer_evals(data, domain.d1.size());
154        evals_to_polynomial_and_commitment(answer, domain.d1, srs)
155    };
156
157    curve_sponge.absorb_g(&[data_comm.cm, *query_comm, answer_comm]);
158
159    // coefficient form, over d4? d2?
160    // quotient_Poly has degree d1
161    let quotient_poly: DensePolynomial<ScalarField> = {
162        let data_d2 = data_poly.evaluate_over_domain_by_ref(domain.d2);
163        let query_d2 = query_poly.evaluate_over_domain_by_ref(domain.d2);
164        let answer_d2 = answer_poly.evaluate_over_domain_by_ref(domain.d2);
165
166        // q×d - a
167        let numerator_eval: Evaluations<ScalarField, R2D<ScalarField>> =
168            &(&data_d2 * &query_d2) - &answer_d2;
169
170        let numerator_eval_interpolated = numerator_eval.interpolate();
171
172        // We compute the polynomial t(X) by dividing the constraints polynomial
173        // by the vanishing polynomial, i.e. Z_H(X).
174        let (quotient, res) = numerator_eval_interpolated.divide_by_vanishing_poly(domain.d1);
175        // As the constraints must be verified on H, the rest of the division
176        // must be equal to 0 as the constraints polynomial and Z_H(X) are both
177        // equal on H.
178        if !res.is_zero() {
179            panic!("Division by vanishing polynomial gave a non-zero remainder.");
180        }
181
182        quotient
183    };
184
185    // commit to the quotient polynomial $t$.
186    // num_chunks = 1 because our constraint is degree 2, which makes the quotient polynomial of degree d1
187    let quotient_comm = commit_poly(srs, &quotient_poly);
188    curve_sponge.absorb_g(&[quotient_comm]);
189
190    // aka zeta
191    let evaluation_point = curve_sponge.challenge();
192
193    // Fiat Shamir - absorbing evaluations
194    let mut scalar_sponge = CurveScalarSponge::new(Curve::sponge_params());
195    scalar_sponge.absorb(&curve_sponge.clone().digest());
196
197    let data_eval = data_poly.evaluate(&evaluation_point);
198    let query_eval = query_poly.evaluate(&evaluation_point);
199    let answer_eval = answer_poly.evaluate(&evaluation_point);
200    let quotient_eval = quotient_poly.evaluate(&evaluation_point);
201
202    for eval in [data_eval, query_eval, answer_eval, quotient_eval].into_iter() {
203        scalar_sponge.absorb(&eval);
204    }
205
206    let (_, endo_r) = Curve::endos();
207    // Generate scalars used as combiners for sub-statements within our IPA opening proof.
208    let polyscale = scalar_sponge.challenge().to_field(endo_r);
209    let evalscale = scalar_sponge.challenge().to_field(endo_r);
210
211    // Creating the polynomials for the batch proof
212    // Gathering all polynomials to use in the opening proof
213    let opening_proof_inputs: Vec<_> = {
214        let coefficients_form =
215            DensePolynomialOrEvaluations::<_, R2D<ScalarField>>::DensePolynomial;
216        let non_hiding = |n_chunks| PolyComm {
217            chunks: vec![ScalarField::zero(); n_chunks],
218        };
219
220        vec![
221            (coefficients_form(&data_poly), non_hiding(1)),
222            (coefficients_form(&query_poly), non_hiding(1)),
223            (coefficients_form(&answer_poly), non_hiding(1)),
224            (coefficients_form(&quotient_poly), non_hiding(1)),
225        ]
226    };
227
228    let opening_proof = srs.open(
229        group_map,
230        opening_proof_inputs.as_slice(),
231        &[evaluation_point],
232        polyscale,
233        evalscale,
234        curve_sponge,
235        rng,
236    );
237
238    ReadProof {
239        answer_comm,
240        quotient_comm,
241        data_eval,
242        query_eval,
243        answer_eval,
244        opening_proof,
245    }
246}
247
248pub fn verify<RNG>(
249    srs: &SRS<Curve>,
250    domain: EvaluationDomains<ScalarField>,
251    group_map: &<Curve as CommitmentCurve>::Map,
252    rng: &mut RNG,
253    // Commitment to data
254    data_comm: &Commitment<Curve>,
255    // Commitment to query
256    query_comm: &Curve,
257    proof: &ReadProof,
258) -> bool
259where
260    RNG: RngCore + CryptoRng,
261{
262    let mut curve_sponge = CurveSponge::new(Curve::other_curve_sponge_params());
263    curve_sponge.absorb_g(&[
264        data_comm.cm,
265        *query_comm,
266        proof.answer_comm,
267        proof.quotient_comm,
268    ]);
269
270    let evaluation_point = curve_sponge.challenge();
271
272    let mut scalar_sponge = CurveScalarSponge::new(Curve::sponge_params());
273    scalar_sponge.absorb(&curve_sponge.clone().digest());
274
275    let vanishing_poly_at_zeta = domain.d1.vanishing_polynomial().evaluate(&evaluation_point);
276    let quotient_eval = {
277        (proof.data_eval * proof.query_eval - proof.answer_eval)
278            * vanishing_poly_at_zeta
279                .inverse()
280                .unwrap_or_else(|| panic!("Inverse fails only with negligible probability"))
281    };
282
283    for eval in [
284        proof.data_eval,
285        proof.query_eval,
286        proof.answer_eval,
287        quotient_eval,
288    ]
289    .into_iter()
290    {
291        scalar_sponge.absorb(&eval);
292    }
293
294    let (_, endo_r) = Curve::endos();
295    // Generate scalars used as combiners for sub-statements within our IPA opening proof.
296    let polyscale = scalar_sponge.challenge().to_field(endo_r);
297    let evalscale = scalar_sponge.challenge().to_field(endo_r);
298
299    let coms_and_evaluations = vec![
300        Evaluation {
301            commitment: PolyComm {
302                chunks: vec![data_comm.cm],
303            },
304            evaluations: vec![vec![proof.data_eval]],
305        },
306        Evaluation {
307            commitment: PolyComm {
308                chunks: vec![*query_comm],
309            },
310            evaluations: vec![vec![proof.query_eval]],
311        },
312        Evaluation {
313            commitment: PolyComm {
314                chunks: vec![proof.answer_comm],
315            },
316            evaluations: vec![vec![proof.answer_eval]],
317        },
318        Evaluation {
319            commitment: PolyComm {
320                chunks: vec![proof.quotient_comm],
321            },
322            evaluations: vec![vec![quotient_eval]],
323        },
324    ];
325    let combined_inner_product = {
326        let evaluations: Vec<_> = coms_and_evaluations
327            .iter()
328            .map(|Evaluation { evaluations, .. }| evaluations.clone())
329            .collect();
330
331        combined_inner_product(&polyscale, &evalscale, evaluations.as_slice())
332    };
333
334    srs.verify(
335        group_map,
336        &mut [BatchEvaluationProof {
337            sponge: curve_sponge,
338            evaluation_points: vec![evaluation_point],
339            polyscale,
340            evalscale,
341            evaluations: coms_and_evaluations,
342            opening: &proof.opening_proof,
343            combined_inner_product,
344        }],
345        rng,
346    )
347}
348
349/// Checks that the provided answer is consistent with the proof
350/// Here, we just recompute the commitment
351/// TODO: could we just recompute the evaluation ?
352pub fn verify_answer(srs: &SRS<Curve>, query: &Query, answer: &Answer, proof: &ReadProof) -> bool {
353    let answer_comm = answer.to_commitment(query, srs);
354    answer_comm == proof.answer_comm
355}
356
357#[cfg(test)]
358mod tests {
359    use super::*;
360    use crate::{Curve, ScalarField, SRS_SIZE};
361    use ark_ec::AffineRepr;
362    use ark_ff::{One, UniformRand};
363    use kimchi::{circuits::domains::EvaluationDomains, groupmap::GroupMap};
364    use poly_commitment::{commitment::CommitmentCurve, SRS as _};
365
366    #[test]
367    fn test_read_proof_completeness_soundness() {
368        let mut rng = o1_utils::tests::make_test_rng(None);
369
370        let srs = poly_commitment::precomputed_srs::get_srs_test();
371        let group_map = <Curve as CommitmentCurve>::Map::setup();
372        let domain: EvaluationDomains<ScalarField> = EvaluationDomains::create(srs.size()).unwrap();
373
374        let data = {
375            let mut data = vec![];
376            (0..SRS_SIZE).for_each(|_| data.push(ScalarField::rand(&mut rng)));
377            Data { data }
378        };
379
380        let data_comm = data.to_commitment(&srs);
381
382        let query = Query::random(0.1, SRS_SIZE);
383
384        let query_comm = { commit_poly(&srs, &query.to_polynomial(domain.d1)) };
385
386        let query_comm_sparse = query.to_commitment(&srs);
387
388        assert!(
389            query_comm == query_comm_sparse,
390            "Query commitment: commitment should be the same whatever the computation method is."
391        );
392
393        let proof = prove(
394            &srs,
395            domain,
396            &group_map,
397            &mut rng,
398            &data,
399            &query,
400            &data_comm,
401            &query_comm,
402        );
403        let res = verify(
404            &srs,
405            domain,
406            &group_map,
407            &mut rng,
408            &data_comm,
409            &query_comm,
410            &proof,
411        );
412
413        assert!(res, "Completeness: Proof must verify");
414
415        let proof_malformed_1 = ReadProof {
416            answer_comm: Curve::zero(),
417            ..proof.clone()
418        };
419
420        let res_1 = verify(
421            &srs,
422            domain,
423            &group_map,
424            &mut rng,
425            &data_comm,
426            &query_comm,
427            &proof_malformed_1,
428        );
429
430        assert!(!res_1, "Soundness: Malformed proof #1 must NOT verify");
431
432        let proof_malformed_2 = ReadProof {
433            query_eval: ScalarField::one(),
434            ..proof.clone()
435        };
436
437        let res_2 = verify(
438            &srs,
439            domain,
440            &group_map,
441            &mut rng,
442            &data_comm,
443            &query_comm,
444            &proof_malformed_2,
445        );
446
447        assert!(!res_2, "Soundness: Malformed proof #2 must NOT verify");
448
449        let mut wrong_query = query.query.clone();
450        wrong_query.truncate(query.query.len() - 2);
451
452        let proof_for_wrong_query = prove(
453            &srs,
454            domain,
455            &group_map,
456            &mut rng,
457            &data,
458            &Query { query: wrong_query },
459            &data_comm,
460            &query_comm,
461        );
462        let res_3 = verify(
463            &srs,
464            domain,
465            &group_map,
466            &mut rng,
467            &data_comm,
468            &query_comm,
469            &proof_for_wrong_query,
470        );
471
472        assert!(!res_3, "Soundness: Truncated query must NOT verify");
473
474        let mut answer = query.to_answer(&data);
475
476        let res_4 = verify_answer(&srs, &query, &answer, &proof);
477
478        assert!(res_4, "Completeness: Answer must be consistent with proof");
479
480        answer.answer[0] = ScalarField::one();
481
482        let res_5 = verify_answer(&srs, &query, &answer, &proof);
483
484        assert!(!res_5, "Soundness: Wrong answer must NOT verify");
485    }
486}