Skip to content

Commit 1c64bc0

Browse files
authored
Merge pull request o1-labs#3009 from o1-labs/martin/saffron-update-with-diff-type
[saffron] Add update methods with tests
2 parents 39868c4 + dca3c7f commit 1c64bc0

File tree

4 files changed

+117
-20
lines changed

4 files changed

+117
-20
lines changed

saffron/src/blob.rs

Lines changed: 100 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
use crate::{
22
commitment::Commitment,
3+
diff::Diff,
34
utils::{decode_into, encode_for_domain},
45
};
56
use ark_ff::PrimeField;
6-
use ark_poly::{univariate::DensePolynomial, EvaluationDomain, Evaluations};
7+
use ark_poly::{
8+
univariate::DensePolynomial, EvaluationDomain, Evaluations, Radix2EvaluationDomain,
9+
};
710
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
811
use kimchi::curve::KimchiCurve;
912
use mina_poseidon::FqSponge;
@@ -24,7 +27,7 @@ pub struct FieldBlob<G: CommitmentCurve> {
2427
pub domain_size: usize,
2528
pub commitment: Commitment<G>,
2629
#[serde_as(as = "Vec<o1_utils::serialization::SerdeAs>")]
27-
pub data: Vec<DensePolynomial<G::ScalarField>>,
30+
pub chunks: Vec<DensePolynomial<G::ScalarField>>,
2831
}
2932

3033
#[instrument(skip_all, level = "debug")]
@@ -51,29 +54,29 @@ impl<G: KimchiCurve> FieldBlob<G> {
5154
let field_elements = encode_for_domain(&domain, bytes);
5255
let domain_size = domain.size();
5356

54-
let data: Vec<DensePolynomial<G::ScalarField>> = debug_span!("fft").in_scope(|| {
57+
let chunks: Vec<DensePolynomial<G::ScalarField>> = debug_span!("fft").in_scope(|| {
5558
field_elements
5659
.par_iter()
5760
.map(|chunk| Evaluations::from_vec_and_domain(chunk.to_vec(), domain).interpolate())
5861
.collect()
5962
});
6063
let commitment = {
61-
let chunks = commit_to_blob_data(srs, &data);
64+
let chunks = commit_to_blob_data(srs, &chunks);
6265
let mut sponge = EFqSponge::new(G::other_curve_sponge_params());
6366
Commitment::from_chunks(chunks, &mut sponge)
6467
};
6568

6669
debug!(
6770
"Encoded {:.2} MB into {} polynomials",
6871
bytes.len() as f32 / 1_000_000.0,
69-
data.len()
72+
chunks.len()
7073
);
7174

7275
FieldBlob {
7376
n_bytes: bytes.len(),
7477
domain_size,
7578
commitment,
76-
data,
79+
chunks,
7780
}
7881
}
7982

@@ -92,7 +95,7 @@ impl<G: KimchiCurve> FieldBlob<G> {
9295
let mut bytes = Vec::with_capacity(blob.n_bytes);
9396
let mut buffer = vec![0u8; m];
9497

95-
for p in blob.data {
98+
for p in blob.chunks {
9699
let evals = p.evaluate_over_domain(domain).evals;
97100
for x in evals {
98101
decode_into(&mut buffer, x);
@@ -103,14 +106,44 @@ impl<G: KimchiCurve> FieldBlob<G> {
103106
bytes.truncate(blob.n_bytes);
104107
bytes
105108
}
109+
110+
pub fn update<EFqSponge: FqSponge<G::BaseField, G, G::ScalarField>>(
111+
&mut self,
112+
srs: &SRS<G>,
113+
domain: &Radix2EvaluationDomain<G::ScalarField>,
114+
diff: Diff<G::ScalarField>,
115+
) {
116+
let diff_evaluations = diff.as_evaluations(domain);
117+
let commitment = {
118+
let commitment_diffs = diff_evaluations
119+
.par_iter()
120+
.map(|evals| srs.commit_evaluations_non_hiding(*domain, evals))
121+
.collect::<Vec<_>>();
122+
let mut sponge = EFqSponge::new(G::other_curve_sponge_params());
123+
self.commitment.update(commitment_diffs, &mut sponge)
124+
};
125+
let chunks: Vec<DensePolynomial<G::ScalarField>> = diff_evaluations
126+
.into_par_iter()
127+
.zip(self.chunks.par_iter())
128+
.map(|(evals, p)| {
129+
let d_p: DensePolynomial<G::ScalarField> = evals.interpolate();
130+
p + &d_p
131+
})
132+
.collect();
133+
self.commitment = commitment;
134+
self.chunks = chunks;
135+
self.n_bytes = diff.new_byte_len;
136+
}
106137
}
107138

108139
#[cfg(test)]
109140
mod tests {
110141
use crate::{commitment::commit_to_field_elems, env};
111142

112143
use super::*;
113-
use crate::utils::test_utils::*;
144+
use crate::{diff::tests::*, utils::test_utils::*};
145+
use ark_ec::AffineRepr;
146+
use ark_ff::Zero;
114147
use ark_poly::Radix2EvaluationDomain;
115148
use mina_curves::pasta::{Fp, Vesta, VestaParameters};
116149
use mina_poseidon::{constants::PlonkSpongeConstantsKimchi, sponge::DefaultFqSponge};
@@ -149,11 +182,64 @@ mod tests {
149182
proptest! {
150183
#![proptest_config(ProptestConfig::with_cases(10))]
151184
#[test]
152-
fn test_user_and_storage_provider_commitments_equal(UserData(xs) in UserData::arbitrary())
153-
{ let elems = encode_for_domain(&*DOMAIN, &xs);
154-
let user_commitments = commit_to_field_elems::<_, VestaFqSponge>(&*SRS, *DOMAIN, elems);
155-
let blob = FieldBlob::<Vesta>::encode::<_, VestaFqSponge>(&*SRS, *DOMAIN, &xs);
156-
prop_assert_eq!(user_commitments, blob.commitment);
157-
}
185+
fn test_user_and_storage_provider_commitments_equal(UserData(xs) in UserData::arbitrary())
186+
{ let elems = encode_for_domain(&*DOMAIN, &xs);
187+
let user_commitments = commit_to_field_elems::<_, VestaFqSponge>(&*SRS, *DOMAIN, elems);
188+
let blob = FieldBlob::<Vesta>::encode::<_, VestaFqSponge>(&*SRS, *DOMAIN, &xs);
189+
prop_assert_eq!(user_commitments, blob.commitment);
190+
}
191+
}
192+
193+
fn encode_to_chunk_size(xs: &[u8], chunk_size: usize) -> FieldBlob<Vesta> {
194+
let mut blob = FieldBlob::<Vesta>::encode::<_, VestaFqSponge>(&*SRS, *DOMAIN, xs);
195+
assert!(blob.chunks.len() <= chunk_size);
196+
{
197+
let pad = DensePolynomial::zero();
198+
blob.chunks.resize(chunk_size, pad);
199+
}
200+
{
201+
let pad = PolyComm::new(vec![Vesta::zero()]);
202+
let mut commitments = blob.commitment.chunks.clone();
203+
commitments.resize(chunk_size, pad);
204+
let mut sponge = VestaFqSponge::new(Vesta::other_curve_sponge_params());
205+
blob.commitment = Commitment::from_chunks(commitments, &mut sponge);
158206
}
207+
blob
208+
}
209+
210+
proptest! {
211+
#![proptest_config(ProptestConfig::with_cases(20))]
212+
#[test]
213+
214+
fn test_allow_legal_updates((UserData(xs), UserData(ys)) in
215+
(UserData::arbitrary().prop_flat_map(random_diff))
216+
) {
217+
// start with some random user data
218+
let mut xs_blob = FieldBlob::<Vesta>::encode::<_, VestaFqSponge>(&*SRS, *DOMAIN, &xs);
219+
let diff = Diff::<Fp>::create(&*DOMAIN, &xs, &ys).unwrap();
220+
xs_blob.update::<VestaFqSponge>(&*SRS, &*DOMAIN, diff.clone());
221+
222+
// check that the user and SP agree on the new data
223+
let user_commitment = {
224+
let elems = encode_for_domain(&*DOMAIN, &xs);
225+
let commitment = commit_to_field_elems::<Vesta, VestaFqSponge>(&*SRS, *DOMAIN, elems);
226+
227+
let commitment_diffs = diff.as_evaluations(&*DOMAIN)
228+
.par_iter()
229+
.map(|evals| SRS.commit_evaluations_non_hiding(*DOMAIN, evals))
230+
.collect::<Vec<_>>();
231+
232+
let mut sponge = VestaFqSponge::new(Vesta::other_curve_sponge_params());
233+
commitment.update(commitment_diffs, &mut sponge)
234+
235+
};
236+
237+
let ys_blob = encode_to_chunk_size(&ys, xs_blob.chunks.len());
238+
prop_assert_eq!(user_commitment.clone(), ys_blob.commitment.clone());
239+
240+
// the updated blob should be the same as if we just start with the new data
241+
prop_assert_eq!(xs_blob, ys_blob)
242+
}
243+
244+
}
159245
}

saffron/src/commitment.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use poly_commitment::{
1212
use rayon::prelude::*;
1313
use serde::{Deserialize, Serialize};
1414
use serde_with::serde_as;
15+
use std::ops::Add;
1516
use tracing::instrument;
1617

1718
#[serde_as]
@@ -36,6 +37,14 @@ impl<G: KimchiCurve> Commitment<G> {
3637
folded,
3738
}
3839
}
40+
41+
pub fn update<EFqSponge>(&self, diff: Vec<PolyComm<G>>, sponge: &mut EFqSponge) -> Self
42+
where
43+
EFqSponge: FqSponge<G::BaseField, G, G::ScalarField>,
44+
{
45+
let new_chunks = self.chunks.iter().zip(diff).map(|(g, d)| g.add(&d));
46+
Self::from_chunks(new_chunks.collect(), sponge)
47+
}
3948
}
4049

4150
#[instrument(skip_all, level = "debug")]

saffron/src/diff.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ use tracing::instrument;
88
// sparse representation, keeping only the non-zero differences
99
#[derive(Clone, Debug, PartialEq)]
1010
pub struct Diff<F: PrimeField> {
11-
pub evaluation_diffs: Vec<Vec<(usize, F)>>,
11+
pub chunks: Vec<Vec<(usize, F)>>,
12+
pub new_byte_len: usize,
1213
}
1314

1415
#[derive(Debug, Error, Clone, PartialEq)]
@@ -40,7 +41,8 @@ impl<F: PrimeField> Diff<F> {
4041
new_elems.resize(old_elems.len(), padding);
4142
}
4243
Ok(Diff {
43-
evaluation_diffs: new_elems
44+
new_byte_len: new.len(),
45+
chunks: new_elems
4446
.par_iter()
4547
.zip(old_elems)
4648
.map(|(n, o)| {
@@ -60,7 +62,7 @@ impl<F: PrimeField> Diff<F> {
6062
&self,
6163
domain: &Radix2EvaluationDomain<F>,
6264
) -> Vec<Evaluations<F, Radix2EvaluationDomain<F>>> {
63-
self.evaluation_diffs
65+
self.chunks
6466
.par_iter()
6567
.map(|diff| {
6668
let mut evals = vec![F::zero(); domain.size()];
@@ -117,7 +119,7 @@ pub mod tests {
117119
fn add(mut evals: Vec<Vec<Fp>>, diff: &Diff<Fp>) -> Vec<Vec<Fp>> {
118120
evals
119121
.par_iter_mut()
120-
.zip(diff.evaluation_diffs.par_iter())
122+
.zip(diff.chunks.par_iter())
121123
.for_each(|(eval_chunk, diff_chunk)| {
122124
diff_chunk.iter().for_each(|(j, val)| {
123125
eval_chunk[*j] += val;
@@ -130,7 +132,7 @@ pub mod tests {
130132
#![proptest_config(ProptestConfig::with_cases(20))]
131133
#[test]
132134

133-
fn test_allow_legal_updates((UserData(xs), UserData(ys)) in
135+
fn test_allow_legal_diff((UserData(xs), UserData(ys)) in
134136
(UserData::arbitrary().prop_flat_map(random_diff))
135137
) {
136138
let diff = Diff::<Fp>::create(&*DOMAIN, &xs, &ys);

saffron/src/proof.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ where
3939
{
4040
let p = {
4141
let init = (DensePolynomial::zero(), G::ScalarField::one());
42-
blob.data
42+
blob.chunks
4343
.into_iter()
4444
.fold(init, |(acc_poly, curr_power), curr_poly| {
4545
(

0 commit comments

Comments
 (0)