1use crate::{blob::FieldBlob, utils, Curve, CurveFqSponge, CurveFrSponge, ScalarField, SRS_SIZE};
14use ark_ec::AffineRepr;
15use ark_ff::{One, Zero};
16use ark_poly::{
17 EvaluationDomain, Evaluations, Polynomial, Radix2EvaluationDomain as D, Radix2EvaluationDomain,
18};
19use kimchi::{curve::KimchiCurve, plonk_sponge::FrSponge};
20use mina_poseidon::FqSponge;
21use poly_commitment::{
22 commitment::{BatchEvaluationProof, CommitmentCurve, Evaluation},
23 ipa::{OpeningProof, SRS},
24 utils::DensePolynomialOrEvaluations,
25 PolyComm,
26};
27use rand::rngs::OsRng;
28use rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator};
29use serde::{Deserialize, Serialize};
30use serde_with::serde_as;
31use tracing::instrument;
32
33#[serde_as]
34#[derive(Debug, Serialize, Deserialize, Clone)]
35pub struct StorageProof {
36 #[serde_as(as = "o1_utils::serialization::SerdeAs")]
37 pub combined_data_eval: ScalarField,
38 pub opening_proof: OpeningProof<Curve>,
39}
40
41#[instrument(skip_all, level = "debug")]
42pub fn prove(
43 srs: &SRS<Curve>,
44 group_map: &<Curve as CommitmentCurve>::Map,
45 blob: FieldBlob,
46 challenge: ScalarField,
47 rng: &mut OsRng,
48) -> StorageProof {
49 let domain = Radix2EvaluationDomain::new(SRS_SIZE).unwrap();
51
52 let final_chunk = (blob.data.len() / SRS_SIZE) - 1;
53 assert!(blob.data.len() % SRS_SIZE == 0);
54
55 let combined_data_commitment =
57 utils::aggregate_commitments(challenge, blob.commitments.as_slice());
58
59 let combined_data: Vec<ScalarField> = {
63 let mut initial: Vec<ScalarField> =
64 blob.data[final_chunk * SRS_SIZE..(final_chunk + 1) * SRS_SIZE].to_vec();
65
66 (0..final_chunk).rev().for_each(|chunk_ix| {
67 initial.par_iter_mut().enumerate().for_each(|(idx, acc)| {
68 *acc *= challenge;
69 *acc += blob.data[chunk_ix * SRS_SIZE + idx];
70 });
71 });
72
73 initial
74 };
75
76 let mut fq_sponge = CurveFqSponge::new(Curve::other_curve_sponge_params());
77 fq_sponge.absorb_g(&[combined_data_commitment]);
78 let evaluation_point = fq_sponge.squeeze(2);
79
80 let combined_data_poly = Evaluations::from_vec_and_domain(combined_data, domain).interpolate();
81 let combined_data_eval = combined_data_poly.evaluate(&evaluation_point);
82
83 let fq_sponge_before_evaluations = fq_sponge.clone();
85 let mut fr_sponge = CurveFrSponge::new(Curve::sponge_params());
86 fr_sponge.absorb(&fq_sponge.digest());
87
88 fr_sponge.absorb(&combined_data_eval);
91
92 let opening_proof =
93 srs.open(
94 group_map,
95 &[
96 (
97 DensePolynomialOrEvaluations::<
98 <Curve as AffineRepr>::ScalarField,
99 D<ScalarField>,
100 >::DensePolynomial(&combined_data_poly),
101 PolyComm {
102 chunks: vec![ScalarField::zero()],
103 },
104 ),
105 ],
106 &[evaluation_point],
107 ScalarField::one(), ScalarField::one(), fq_sponge_before_evaluations,
110 rng,
111 );
112
113 StorageProof {
114 combined_data_eval,
115 opening_proof,
116 }
117}
118
119#[instrument(skip_all, level = "debug")]
120pub fn verify_wrt_combined_data_commitment(
121 srs: &SRS<Curve>,
122 group_map: &<Curve as CommitmentCurve>::Map,
123 combined_data_commitment: Curve,
124 proof: &StorageProof,
125 rng: &mut OsRng,
126) -> bool {
127 let mut fq_sponge = CurveFqSponge::new(Curve::other_curve_sponge_params());
128 let evaluation_point = {
129 fq_sponge.absorb_g(&[combined_data_commitment]);
130 fq_sponge.squeeze(2)
131 };
132
133 let fq_sponge_before_evaluations = fq_sponge.clone();
134 let mut fr_sponge = CurveFrSponge::new(Curve::sponge_params());
135 fr_sponge.absorb(&fq_sponge.digest());
136
137 fr_sponge.absorb(&proof.combined_data_eval);
140
141 srs.verify(
142 group_map,
143 &mut [BatchEvaluationProof {
144 sponge: fq_sponge_before_evaluations,
145 evaluation_points: vec![evaluation_point],
146 polyscale: ScalarField::one(),
147 evalscale: ScalarField::one(),
148 evaluations: vec![Evaluation {
149 commitment: PolyComm {
150 chunks: vec![combined_data_commitment],
151 },
152 evaluations: vec![vec![proof.combined_data_eval]],
153 }],
154 opening: &proof.opening_proof,
155 combined_inner_product: proof.combined_data_eval,
156 }],
157 rng,
158 )
159}
160
161#[instrument(skip_all, level = "debug")]
162pub fn verify(
163 srs: &SRS<Curve>,
164 group_map: &<Curve as CommitmentCurve>::Map,
165 commitments: &[Curve],
166 challenge: ScalarField, proof: &StorageProof,
168 rng: &mut OsRng,
169) -> bool {
170 let combined_data_commitment = utils::aggregate_commitments(challenge, commitments);
172 verify_wrt_combined_data_commitment(srs, group_map, combined_data_commitment, proof, rng)
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178 use crate::{
179 commitment::{combine_commitments, commit_to_field_elems},
180 encoding::encode_for_domain,
181 utils::test_utils::UserData,
182 };
183
184 use ark_ff::UniformRand;
185 use ark_poly::{EvaluationDomain, Radix2EvaluationDomain};
186 use kimchi::groupmap::GroupMap;
187 use mina_curves::pasta::{Fp, Vesta};
188 use once_cell::sync::Lazy;
189 use poly_commitment::{commitment::CommitmentCurve, ipa::SRS, SRS as _};
190 use proptest::prelude::*;
191
192 static SRS: Lazy<SRS<Vesta>> = Lazy::new(poly_commitment::precomputed_srs::get_srs_test);
195
196 static DOMAIN: Lazy<Radix2EvaluationDomain<Fp>> =
197 Lazy::new(|| Radix2EvaluationDomain::new(SRS.size()).unwrap());
198
199 static GROUP_MAP: Lazy<<Vesta as CommitmentCurve>::Map> =
200 Lazy::new(<Vesta as CommitmentCurve>::Map::setup);
201
202 proptest! {
203 #![proptest_config(ProptestConfig::with_cases(5))]
204 #[test]
205 fn test_storage_prove_verify(UserData(data) in UserData::arbitrary()) {
206 let mut rng = OsRng;
207 let commitments = {
208 let field_elems: Vec<_> = encode_for_domain(DOMAIN.size(), &data).into_iter().flatten().collect();
209 commit_to_field_elems(&SRS, &field_elems)
210 };
211
212 let challenge_seed: ScalarField = ScalarField::rand(&mut rng);
214
215 let mut sponge = CurveFqSponge::new(Curve::other_curve_sponge_params());
216 sponge.absorb_fr(&[challenge_seed]);
217 let (combined_data_commitment, challenge) =
218 combine_commitments(&mut sponge, commitments.as_slice());
219
220 let blob = FieldBlob::from_bytes::<_>(&SRS, *DOMAIN, &data);
221
222 let proof = prove(&SRS, &GROUP_MAP, blob, challenge, &mut rng);
223 let res = verify_wrt_combined_data_commitment(
224 &SRS,
225 &GROUP_MAP,
226 combined_data_commitment,
227 &proof,
228 &mut rng,
229 );
230 prop_assert!(res);
231 }
232 }
233
234 proptest! {
235 #![proptest_config(ProptestConfig::with_cases(5))]
236 #[test]
237 fn test_storage_soundness(UserData(data) in UserData::arbitrary()) {
238 let mut rng = OsRng;
239 let commitments = {
240 let field_elems: Vec<_> = encode_for_domain(DOMAIN.size(), &data).into_iter().flatten().collect();
241 commit_to_field_elems(&SRS, &field_elems)
242 };
243
244 let challenge_seed: ScalarField = ScalarField::rand(&mut rng);
246
247 let mut sponge = CurveFqSponge::new(Curve::other_curve_sponge_params());
248 sponge.absorb_fr(&[challenge_seed]);
249 let (combined_data_commitment, challenge) =
250 combine_commitments(&mut sponge, commitments.as_slice());
251
252 let blob = FieldBlob::from_bytes::<_>(&SRS, *DOMAIN, &data);
253
254 let proof = prove(&SRS, &GROUP_MAP, blob, challenge, &mut rng);
255
256 let proof_malformed_1 = {
257 StorageProof {
258 combined_data_eval: proof.combined_data_eval + ScalarField::one(),
259 opening_proof: proof.opening_proof.clone(),
260 }
261 };
262
263 let res_1 = verify_wrt_combined_data_commitment(
264 &SRS,
265 &GROUP_MAP,
266 combined_data_commitment,
267 &proof_malformed_1,
268 &mut rng,
269 );
270
271 prop_assert!(!res_1);
272
273 let proof_malformed_2 = {
274 let mut opening_proof = proof.opening_proof.clone();
275 opening_proof.z1 = ScalarField::one();
276 StorageProof {
277 combined_data_eval: proof.combined_data_eval,
278 opening_proof,
279 }
280 };
281
282 let res_2 = verify_wrt_combined_data_commitment(
283 &SRS,
284 &GROUP_MAP,
285 combined_data_commitment,
286 &proof_malformed_2,
287 &mut rng,
288 );
289
290 prop_assert!(!res_2);
291 }
292 }
293}