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 assert!(
36 old.len() == new.len(),
37 "Input 'old' and 'new' must have the same number of chunks"
38 );
39
40 let diffs: Vec<Diff<_>> = old
41 .par_iter()
42 .zip(new)
43 .enumerate()
44 .filter_map(|(region, (o, n))| {
45 let mut addresses: Vec<u64> = vec![];
46 let mut new_values: Vec<F> = vec![];
47 for (index, (o_elem, n_elem)) in o.iter().zip(n.iter()).enumerate() {
48 if o_elem != n_elem {
49 addresses.push(index as u64);
50 new_values.push(*n_elem);
51 }
52 }
53
54 if !addresses.is_empty() {
55 Some(Diff {
56 region: region as u64,
57 addresses,
58 new_values,
59 })
60 } else {
61 None
63 }
64 })
65 .collect();
66
67 Ok(diffs)
68 }
69
70 #[instrument(skip_all, level = "debug")]
71 pub fn create_from_bytes<D: EvaluationDomain<F>>(
72 domain: &D,
73 old: &[u8],
74 new: &[u8],
75 ) -> Result<Vec<Diff<F>>, DiffError> {
76 let old_elems: Vec<Vec<F>> = encode_for_domain(domain.size(), old);
77 let mut new_elems: Vec<Vec<F>> = encode_for_domain(domain.size(), new);
78 if old_elems.len() < new_elems.len() {
79 return Err(DiffError::CapacityMismatch {
80 max_number_chunks: old_elems.len(),
81 attempted: new_elems.len(),
82 });
83 }
84 if old_elems.len() > new_elems.len() {
85 let padding = vec![F::zero(); domain.size()];
86 new_elems.resize(old_elems.len(), padding);
87 }
88
89 Self::create_from_field_elements(&old_elems, &new_elems)
90 }
91}
92
93#[cfg(test)]
94pub mod tests {
95 use super::*;
96 use crate::utils::{chunk_size_in_bytes, min_encoding_chunks, test_utils::UserData};
97 use ark_ff::Zero;
98 use ark_poly::{EvaluationDomain, Radix2EvaluationDomain};
99 use mina_curves::pasta::Fp;
100 use once_cell::sync::Lazy;
101 use proptest::prelude::*;
102 use rand::Rng;
103
104 static DOMAIN: Lazy<Radix2EvaluationDomain<Fp>> =
105 Lazy::new(|| Radix2EvaluationDomain::new(1 << 16).unwrap());
106
107 pub fn randomize_data(threshold: f64, data: &[u8]) -> Vec<u8> {
108 let mut rng = rand::thread_rng();
109 data.iter()
110 .map(|b| {
111 let n = rng.gen::<f64>();
112 if n < threshold {
113 rng.gen::<u8>()
114 } else {
115 *b
116 }
117 })
118 .collect()
119 }
120
121 pub fn random_diff(UserData(xs): UserData) -> BoxedStrategy<(UserData, UserData)> {
122 let n_chunks = min_encoding_chunks(&*DOMAIN, &xs);
123 let max_byte_len = n_chunks * chunk_size_in_bytes(&*DOMAIN);
124 (0.0..=1.0, 0..=max_byte_len)
125 .prop_flat_map(move |(threshold, n)| {
126 let mut ys = randomize_data(threshold, &xs);
127 ys.resize_with(n, rand::random);
129 Just((UserData(xs.clone()), UserData(ys)))
130 })
131 .boxed()
132 }
133
134 fn add_diff_to_data(data: &mut [Vec<Fp>], diff: &Diff<Fp>) {
136 for (addr, new_value) in diff.addresses.iter().zip(diff.new_values.iter()) {
137 data[diff.region as usize][*addr as usize] = *new_value;
138 }
139 }
140
141 proptest! {
142 #![proptest_config(ProptestConfig::with_cases(20))]
143 #[test]
144
145 fn test_allow_legal_diff((UserData(xs), UserData(ys)) in
146 (UserData::arbitrary().prop_flat_map(random_diff))
147 ) {
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 = {
154 let pad = vec![Fp::zero(); DOMAIN.size()];
155 let mut elems = encode_for_domain(DOMAIN.size(), &ys);
156 elems.resize(xs_elems.len(), pad);
157 elems
158 };
159 assert!(xs_elems.len() == ys_elems.len());
160
161 let mut result = xs_elems.clone();
162 for diff in diffs.into_iter() {
163 add_diff_to_data(&mut result, &diff);
164 }
165 prop_assert_eq!(result, ys_elems);
166 }
167 }
168
169 proptest! {
171 #![proptest_config(ProptestConfig::with_cases(10))]
172 #[test]
173 fn test_cannot_construct_bad_diff(
174 (threshold, (UserData(data), UserData(mut extra))) in (
175 0.0..1.0,
176 UserData::arbitrary().prop_flat_map(|UserData(d1)| {
177 UserData::arbitrary()
178 .prop_filter_map(
179 "length constraint", {
180 move |UserData(d2)| {
181 let combined = &[d1.as_slice(), d2.as_slice()].concat();
182 if min_encoding_chunks(&*DOMAIN, &d1) <
183 min_encoding_chunks(&*DOMAIN, combined) {
184 Some((UserData(d1.clone()), UserData(d2)))
185 } else {
186 None
187 }
188 }
189 }
190 )
191 })
192 )
193 ) {
194 let mut ys = randomize_data(threshold, &data);
195 ys.append(&mut extra);
196 let diff = Diff::<Fp>::create_from_bytes(&*DOMAIN, &data, &ys);
197 prop_assert!(diff.is_err());
198 }
199 }
200}