1#![doc(
9 html_logo_url = "https://commonware.xyz/imgs/rustdoc_logo.svg",
10 html_favicon_url = "https://commonware.xyz/favicon.ico"
11)]
12
13use bytes::Buf;
14use commonware_codec::{Codec, FixedSize, Read, Write};
15use std::fmt::Debug;
16
17mod reed_solomon;
18use commonware_cryptography::Digest;
19pub use reed_solomon::{Error as ReedSolomonError, ReedSolomon};
20
21mod no_coding;
22pub use no_coding::{NoCoding, NoCodingError};
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
26pub struct Config {
27 pub minimum_shards: u16,
29 pub extra_shards: u16,
35}
36
37impl Config {
38 pub fn total_shards(&self) -> u16 {
40 self.minimum_shards + self.extra_shards
41 }
42}
43
44impl FixedSize for Config {
45 const SIZE: usize = 2 * <u16 as FixedSize>::SIZE;
46}
47
48impl Write for Config {
49 fn write(&self, buf: &mut impl bytes::BufMut) {
50 self.minimum_shards.write(buf);
51 self.extra_shards.write(buf);
52 }
53}
54
55impl Read for Config {
56 type Cfg = ();
57
58 fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, commonware_codec::Error> {
59 Ok(Self {
60 minimum_shards: u16::read_cfg(buf, cfg)?,
61 extra_shards: u16::read_cfg(buf, cfg)?,
62 })
63 }
64}
65
66pub trait Scheme: Debug + Clone + Send + Sync + 'static {
108 type Commitment: Digest;
110 type Shard: Clone + Eq + Codec + Send + Sync + 'static;
112 type ReShard: Clone + Eq + Codec + Send + Sync + 'static;
118 type CheckingData: Clone + Send;
120 type CheckedShard;
125 type Error: std::fmt::Debug;
126
127 #[allow(clippy::type_complexity)]
132 fn encode(
133 config: &Config,
134 data: impl Buf,
135 ) -> Result<(Self::Commitment, Vec<Self::Shard>), Self::Error>;
136
137 #[allow(clippy::type_complexity)]
147 fn reshard(
148 config: &Config,
149 commitment: &Self::Commitment,
150 index: u16,
151 shard: Self::Shard,
152 ) -> Result<(Self::CheckingData, Self::CheckedShard, Self::ReShard), Self::Error>;
153
154 fn check(
161 config: &Config,
162 commitment: &Self::Commitment,
163 checking_data: &Self::CheckingData,
164 index: u16,
165 reshard: Self::ReShard,
166 ) -> Result<Self::CheckedShard, Self::Error>;
167
168 fn decode(
179 config: &Config,
180 commitment: &Self::Commitment,
181 checking_data: Self::CheckingData,
182 shards: &[Self::CheckedShard],
183 ) -> Result<Vec<u8>, Self::Error>;
184}
185
186pub trait ValidatingScheme: Scheme {}
192
193#[cfg(test)]
194mod test {
195 use super::*;
196 use crate::reed_solomon::ReedSolomon;
197 use commonware_cryptography::Sha256;
198
199 fn test_basic<S: Scheme>() {
200 let data = b"Hello, Reed-Solomon!";
201 let config = Config {
202 minimum_shards: 4,
203 extra_shards: 3,
204 };
205
206 let (commitment, shards) = S::encode(&config, data.as_slice()).unwrap();
208
209 let (mut checking_data, checked_shards): (Vec<_>, Vec<_>) = shards
210 .into_iter()
211 .enumerate()
212 .map(|(i, shard)| {
213 let (checking_data, checked_shard, _) =
214 S::reshard(&config, &commitment, i as u16, shard).unwrap();
215 (checking_data, checked_shard)
216 })
217 .collect();
218
219 let decoded = S::decode(
220 &config,
221 &commitment,
222 checking_data.pop().unwrap(),
223 &checked_shards[..config.minimum_shards as usize],
224 )
225 .unwrap();
226 assert_eq!(decoded, data, "test_basic_failed");
227 }
228
229 fn test_moderate<S: Scheme>() {
230 let data = b"Testing with more pieces than minimum";
231 let config = Config {
232 minimum_shards: 4,
233 extra_shards: 6,
234 };
235
236 let (commitment, shards) = S::encode(&config, data.as_slice()).unwrap();
238
239 let (mut checking_data, mut checked_shards): (Vec<_>, Vec<_>) = shards
240 .into_iter()
241 .enumerate()
242 .map(|(i, shard)| {
243 let (checking_data, checked_shard, _) =
244 S::reshard(&config, &commitment, i as u16, shard).unwrap();
245 (checking_data, checked_shard)
246 })
247 .collect();
248
249 {
251 let (part1, part2) = checked_shards.split_at_mut(config.minimum_shards as usize);
252 std::mem::swap(&mut part1[0], &mut part2[0]);
253 }
254 let decoded = S::decode(
255 &config,
256 &commitment,
257 checking_data.pop().unwrap(),
258 &checked_shards[..config.minimum_shards as usize],
259 )
260 .unwrap();
261 assert_eq!(decoded, data, "test_moderate_failed");
262 }
263
264 fn test_odd_shard_len<S: Scheme>() {
265 let data = b"a";
266 let config = Config {
267 minimum_shards: 2,
268 extra_shards: 1,
269 };
270
271 let (commitment, shards) = S::encode(&config, data.as_slice()).unwrap();
273
274 let (mut checking_data, checked_shards): (Vec<_>, Vec<_>) = shards
275 .into_iter()
276 .enumerate()
277 .map(|(i, shard)| {
278 let (checking_data, checked_shard, _) =
279 S::reshard(&config, &commitment, i as u16, shard).unwrap();
280 (checking_data, checked_shard)
281 })
282 .collect();
283
284 let decoded = S::decode(
285 &config,
286 &commitment,
287 checking_data.pop().unwrap(),
288 &checked_shards[..config.minimum_shards as usize],
289 )
290 .unwrap();
291 assert_eq!(decoded, data, "test_odd_shard_len_failed");
292 }
293
294 fn test_recovery<S: Scheme>() {
295 let data = b"Testing recovery pieces";
296 let config = Config {
297 minimum_shards: 3,
298 extra_shards: 5,
299 };
300
301 let (commitment, shards) = S::encode(&config, data.as_slice()).unwrap();
303
304 let (mut checking_data, checked_shards): (Vec<_>, Vec<_>) = shards
305 .into_iter()
306 .enumerate()
307 .map(|(i, shard)| {
308 let (checking_data, checked_shard, _) =
309 S::reshard(&config, &commitment, i as u16, shard).unwrap();
310 (checking_data, checked_shard)
311 })
312 .collect();
313
314 let decoded = S::decode(
315 &config,
316 &commitment,
317 checking_data.pop().unwrap(),
318 &checked_shards[checked_shards.len() - config.minimum_shards as usize..],
319 )
320 .unwrap();
321 assert_eq!(decoded, data, "test_recovery_failed");
322 }
323
324 fn test_empty_data<S: Scheme>() {
325 let data = b"";
326 let config = Config {
327 minimum_shards: 30,
328 extra_shards: 100,
329 };
330
331 let (commitment, shards) = S::encode(&config, data.as_slice()).unwrap();
333
334 let (mut checking_data, checked_shards): (Vec<_>, Vec<_>) = shards
335 .into_iter()
336 .enumerate()
337 .map(|(i, shard)| {
338 let (checking_data, checked_shard, _) =
339 S::reshard(&config, &commitment, i as u16, shard).unwrap();
340 (checking_data, checked_shard)
341 })
342 .collect();
343
344 let decoded = S::decode(
345 &config,
346 &commitment,
347 checking_data.pop().unwrap(),
348 &checked_shards[..config.minimum_shards as usize],
349 )
350 .unwrap();
351 assert_eq!(decoded, data, "test_empty_data_failed");
352 }
353
354 fn test_large_data<S: Scheme>() {
355 let data = vec![42u8; 1000]; let config = Config {
357 minimum_shards: 4,
358 extra_shards: 3,
359 };
360
361 let (commitment, shards) = S::encode(&config, data.as_slice()).unwrap();
363
364 let (mut checking_data, checked_shards): (Vec<_>, Vec<_>) = shards
365 .into_iter()
366 .enumerate()
367 .map(|(i, shard)| {
368 let (checking_data, checked_shard, _) =
369 S::reshard(&config, &commitment, i as u16, shard).unwrap();
370 (checking_data, checked_shard)
371 })
372 .collect();
373
374 let decoded = S::decode(
375 &config,
376 &commitment,
377 checking_data.pop().unwrap(),
378 &checked_shards[..config.minimum_shards as usize],
379 )
380 .unwrap();
381 assert_eq!(decoded, data, "test_large_data_failed");
382 }
383
384 fn test_suite<S: Scheme>() {
385 test_basic::<S>();
386 test_moderate::<S>();
387 test_odd_shard_len::<S>();
388 test_recovery::<S>();
389 test_empty_data::<S>();
390 test_large_data::<S>();
391 }
392
393 #[test]
394 fn test_suite_reed_solomon() {
395 test_suite::<ReedSolomon<Sha256>>();
396 }
397
398 #[test]
399 fn test_suite_no_coding() {
400 test_suite::<NoCoding<Sha256>>();
401 }
402}