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