1use crate::utils::encode_for_domain;
2use ark_ff::PrimeField;
3use ark_poly::EvaluationDomain;
4use rayon::prelude::*;
5use thiserror::Error;
6use tracing::instrument;
7
8#[derive(Clone, Debug, PartialEq)]
10pub struct Diff<F: PrimeField> {
11 pub region: u64,
14 pub addresses: Vec<u64>,
16 pub new_values: Vec<F>,
18}
19
20#[derive(Debug, Error, Clone, PartialEq)]
21pub enum DiffError {
22 #[error("Capacity Mismatch: maximum number of chunks is {max_number_chunks}, attempted to create {attempted}")]
23 CapacityMismatch {
24 max_number_chunks: usize,
25 attempted: usize,
26 },
27}
28
29impl<F: PrimeField> Diff<F> {
30 #[instrument(skip_all, level = "debug")]
31 pub fn create_from_field_elements(
32 old: &Vec<Vec<F>>,
33 new: &Vec<Vec<F>>,
34 ) -> Result<Vec<Diff<F>>, DiffError> {
35 if old.len() != new.len() {
36 return Err(DiffError::CapacityMismatch {
37 max_number_chunks: old.len(),
38 attempted: new.len(),
39 });
40 }
41 let diffs: Vec<Diff<_>> = old
42 .par_iter()
43 .zip(new)
44 .enumerate()
45 .filter_map(|(region, (o, n))| {
46 let mut addresses: Vec<u64> = vec![];
47 let mut new_values: Vec<F> = vec![];
48 for (index, (o_elem, n_elem)) in o.iter().zip(n.iter()).enumerate() {
49 if o_elem != n_elem {
50 addresses.push(index as u64);
51 new_values.push(*n_elem);
52 }
53 }
54
55 if !addresses.is_empty() {
56 Some(Diff {
57 region: region as u64,
58 addresses,
59 new_values,
60 })
61 } else {
62 None
64 }
65 })
66 .collect();
67
68 Ok(diffs)
69 }
70
71 #[instrument(skip_all, level = "debug")]
72 pub fn create_from_bytes<D: EvaluationDomain<F>>(
73 domain: &D,
74 old: &[u8],
75 new: &[u8],
76 ) -> Result<Vec<Diff<F>>, DiffError> {
77 let old_elems: Vec<Vec<F>> = encode_for_domain(domain.size(), old);
78 let new_elems: Vec<Vec<F>> = encode_for_domain(domain.size(), new);
79 Self::create_from_field_elements(&old_elems, &new_elems)
80 }
81
82 pub fn apply_inplace(data: &mut [Vec<F>], diff: &Diff<F>) {
85 for (addr, new_value) in diff.addresses.iter().zip(diff.new_values.iter()) {
86 data[diff.region as usize][*addr as usize] = *new_value;
87 }
88 }
89
90 pub fn apply(data: &[Vec<F>], diff: &Diff<F>) -> Vec<Vec<F>> {
93 let mut data = data.to_vec();
94 Self::apply_inplace(&mut data, diff);
95 data
96 }
97}
98
99#[cfg(test)]
100pub mod tests {
101 use super::*;
102 use crate::utils::{chunk_size_in_bytes, min_encoding_chunks, test_utils::UserData};
103 use ark_poly::{EvaluationDomain, Radix2EvaluationDomain};
104 use mina_curves::pasta::Fp;
105 use once_cell::sync::Lazy;
106 use proptest::prelude::*;
107 use rand::Rng;
108
109 static DOMAIN: Lazy<Radix2EvaluationDomain<Fp>> =
110 Lazy::new(|| Radix2EvaluationDomain::new(1 << 16).unwrap());
111
112 pub fn randomize_data(threshold: f64, data: &[u8]) -> Vec<u8> {
113 let mut rng = rand::thread_rng();
114 data.iter()
115 .map(|b| {
116 let n = rng.gen::<f64>();
117 if n < threshold {
118 rng.gen::<u8>()
119 } else {
120 *b
121 }
122 })
123 .collect()
124 }
125
126 pub fn random_diff(UserData(xs): UserData) -> BoxedStrategy<(UserData, UserData)> {
127 let n_chunks = min_encoding_chunks(&*DOMAIN, &xs);
128 let max_byte_len = n_chunks * chunk_size_in_bytes(&*DOMAIN);
129 (0.0..=1.0, 0..=max_byte_len)
130 .prop_flat_map(move |(threshold, n)| {
131 let mut ys = randomize_data(threshold, &xs);
132 ys.resize_with(n, rand::random);
134 Just((UserData(xs.clone()), UserData(ys)))
135 })
136 .boxed()
137 }
138
139 proptest! {
140 #![proptest_config(ProptestConfig::with_cases(20))]
141 #[test]
142
143 fn test_allow_legal_diff((UserData(xs), UserData(ys)) in
144 (UserData::arbitrary().prop_flat_map(random_diff))
145 ) {
146 let min_len = xs.len().min(ys.len());
147 let (xs, ys) = (&xs[..min_len], &ys[..min_len]) ;
148 let diffs = Diff::<Fp>::create_from_bytes(&*DOMAIN, xs, ys);
149 prop_assert!(diffs.is_ok());
150 let diffs = diffs.unwrap();
151
152 let xs_elems = encode_for_domain(DOMAIN.size(), xs);
153 let ys_elems = encode_for_domain(DOMAIN.size(), ys);
154 assert!(xs_elems.len() == ys_elems.len());
155
156 let mut result = xs_elems.clone();
157 for diff in diffs.into_iter() {
158 Diff::apply_inplace(&mut result, &diff);
159 }
160 prop_assert_eq!(result, ys_elems);
161 }
162 }
163
164 proptest! {
166 #![proptest_config(ProptestConfig::with_cases(10))]
167 #[test]
168 fn test_cannot_construct_bad_diff(
169 (threshold, (UserData(data), UserData(mut extra))) in (
170 0.0..1.0,
171 UserData::arbitrary().prop_flat_map(|UserData(d1)| {
172 UserData::arbitrary()
173 .prop_filter_map(
174 "length constraint", {
175 move |UserData(d2)| {
176 let combined = &[d1.as_slice(), d2.as_slice()].concat();
177 if min_encoding_chunks(&*DOMAIN, &d1) <
178 min_encoding_chunks(&*DOMAIN, combined) {
179 Some((UserData(d1.clone()), UserData(d2)))
180 } else {
181 None
182 }
183 }
184 }
185 )
186 })
187 )
188 ) {
189 let mut ys = randomize_data(threshold, &data);
190 ys.append(&mut extra);
191 let diff = Diff::<Fp>::create_from_bytes(&*DOMAIN, &data, &ys);
192 prop_assert!(diff.is_err());
193 }
194 }
195}