1use std::ops::Range;
4
5use jf_merkle_tree::MerkleTreeScheme;
6use p3_maybe_rayon::prelude::*;
7use serde::{Deserialize, Serialize};
8
9use super::{AvidmGf2Commit, AvidmGf2Share};
10use crate::{
11 VidError, VidResult, VidScheme,
12 avidm_gf2::{AvidmGf2Scheme, MerkleTree},
13};
14
15pub struct NsAvidmGf2Scheme;
17
18pub type NsAvidmGf2Commit = super::AvidmGf2Commit;
20pub type NsAvidmGf2Param = super::AvidmGf2Param;
22
23#[derive(Clone, Debug, Hash, Serialize, Deserialize, Eq, PartialEq)]
25pub struct NsAvidmGf2Common {
26 pub param: NsAvidmGf2Param,
28 pub ns_commits: Vec<AvidmGf2Commit>,
30 pub ns_lens: Vec<usize>,
32}
33
34impl NsAvidmGf2Common {
35 pub fn payload_byte_len(&self) -> usize {
37 self.ns_lens.iter().sum()
38 }
39}
40
41#[derive(Clone, Debug, Hash, Serialize, Deserialize, Eq, PartialEq, Default)]
43pub struct NsAvidmGf2Share(pub(crate) Vec<AvidmGf2Share>);
44
45impl NsAvidmGf2Share {
46 pub fn num_nss(&self) -> usize {
48 self.0.len()
49 }
50
51 pub fn weight(&self) -> usize {
53 self.0.first().map_or(0, |share| share.weight())
54 }
55
56 pub fn validate(&self) -> bool {
58 let weight = self.weight();
59 self.0
60 .iter()
61 .all(|share| share.validate() && share.weight() == weight)
62 }
63
64 pub fn contains_ns(&self, ns_index: usize) -> bool {
66 ns_index < self.num_nss()
67 }
68
69 pub fn ns_shares(&self) -> &[AvidmGf2Share] {
71 &self.0
72 }
73
74 pub fn inner_ns_share(&self, ns_index: usize) -> Option<AvidmGf2Share> {
76 self.0.get(ns_index).cloned()
77 }
78
79 pub fn range(&self) -> Option<&Range<usize>> {
82 let first = self.0.first()?.range();
83 self.0
84 .iter()
85 .all(|share| share.range() == first)
86 .then_some(first)
87 }
88}
89
90impl From<Vec<AvidmGf2Share>> for NsAvidmGf2Share {
91 fn from(ns_shares: Vec<AvidmGf2Share>) -> Self {
92 Self(ns_shares)
93 }
94}
95
96impl NsAvidmGf2Scheme {
97 pub fn setup(recovery_threshold: usize, total_weights: usize) -> VidResult<NsAvidmGf2Param> {
99 NsAvidmGf2Param::new(recovery_threshold, total_weights)
100 }
101
102 pub fn commit(
106 param: &NsAvidmGf2Param,
107 payload: &[u8],
108 ns_table: impl IntoIterator<Item = Range<usize>>,
109 ) -> VidResult<(NsAvidmGf2Commit, NsAvidmGf2Common)> {
110 let ns_table = ns_table.into_iter().collect::<Vec<_>>();
111 let ns_lens = ns_table.iter().map(|r| r.len()).collect::<Vec<_>>();
112 let ns_commits = ns_table
113 .into_par_iter()
114 .map(|ns_range| AvidmGf2Scheme::commit(param, &payload[ns_range]))
115 .collect::<Result<Vec<_>, _>>()?;
116 let common = NsAvidmGf2Common {
117 param: param.clone(),
118 ns_commits,
119 ns_lens,
120 };
121 let commit = Self::aggregate_commit(&common.ns_commits)?;
122 Ok((commit, common))
123 }
124
125 pub fn aggregate_commit(ns_commits: &[AvidmGf2Commit]) -> VidResult<NsAvidmGf2Commit> {
131 let commit = MerkleTree::from_elems(None, ns_commits.iter().map(|c| c.commit))
132 .map_err(|err| VidError::Internal(err.into()))?
133 .commitment();
134 Ok(NsAvidmGf2Commit { commit })
135 }
136
137 pub fn is_consistent(commit: &NsAvidmGf2Commit, common: &NsAvidmGf2Common) -> bool {
139 Self::aggregate_commit(&common.ns_commits).is_ok_and(|c| c == *commit)
140 }
141
142 pub fn ns_disperse(
147 param: &NsAvidmGf2Param,
148 distribution: &[u32],
149 payload: &[u8],
150 ns_table: impl IntoIterator<Item = Range<usize>>,
151 ) -> VidResult<(NsAvidmGf2Commit, NsAvidmGf2Common, Vec<NsAvidmGf2Share>)> {
152 let num_storage_nodes = distribution.len();
153 let ns_ranges: Vec<Range<usize>> = ns_table.into_iter().collect();
154 let ns_lens: Vec<usize> = ns_ranges.iter().map(|r| r.len()).collect();
155
156 let per_ns: Vec<(AvidmGf2Commit, Vec<AvidmGf2Share>)> = ns_ranges
161 .par_iter()
162 .map(|ns_range| {
163 AvidmGf2Scheme::disperse(param, distribution, &payload[ns_range.clone()])
164 })
165 .collect::<VidResult<Vec<_>>>()?;
166
167 let (ns_commits, disperses): (Vec<_>, Vec<_>) = per_ns.into_iter().unzip();
168
169 let common = NsAvidmGf2Common {
170 param: param.clone(),
171 ns_commits,
172 ns_lens,
173 };
174 let commit = Self::aggregate_commit(&common.ns_commits)?;
175 let mut shares = vec![NsAvidmGf2Share::default(); num_storage_nodes];
176 disperses.into_iter().for_each(|ns_disperse| {
177 shares
178 .iter_mut()
179 .zip(ns_disperse)
180 .for_each(|(share, ns_share)| share.0.push(ns_share))
181 });
182 Ok((commit, common, shares))
183 }
184
185 pub fn ns_disperse_one(
190 param: &NsAvidmGf2Param,
191 distribution: &[u32],
192 ns_payload: &[u8],
193 ns_index: usize,
194 ) -> VidResult<NsDispersal> {
195 let payload_byte_len = ns_payload.len();
196 let (commit, shares) = AvidmGf2Scheme::disperse(param, distribution, ns_payload)?;
197 Ok(NsDispersal {
198 ns_index,
199 payload_byte_len,
200 commit,
201 shares,
202 })
203 }
204
205 #[cfg(any(test, feature = "testing"))]
211 pub fn ns_disperse_non_codeword(
212 param: &NsAvidmGf2Param,
213 distribution: &[u32],
214 payload: &[u8],
215 ns_table: impl IntoIterator<Item = Range<usize>>,
216 ) -> VidResult<(NsAvidmGf2Commit, NsAvidmGf2Common, Vec<NsAvidmGf2Share>)> {
217 let num_storage_nodes = distribution.len();
218 let ns_ranges: Vec<Range<usize>> = ns_table.into_iter().collect();
219 let ns_lens: Vec<usize> = ns_ranges.iter().map(|r| r.len()).collect();
220 let per_ns: Vec<(AvidmGf2Commit, Vec<AvidmGf2Share>)> = ns_ranges
221 .iter()
222 .map(|ns_range| {
223 AvidmGf2Scheme::disperse_non_codeword(
224 param,
225 distribution,
226 &payload[ns_range.clone()],
227 )
228 })
229 .collect::<VidResult<Vec<_>>>()?;
230 let (ns_commits, disperses): (Vec<_>, Vec<_>) = per_ns.into_iter().unzip();
231 let common = NsAvidmGf2Common {
232 param: param.clone(),
233 ns_commits,
234 ns_lens,
235 };
236 let commit = Self::aggregate_commit(&common.ns_commits)?;
237 let mut shares = vec![NsAvidmGf2Share::default(); num_storage_nodes];
238 disperses.into_iter().for_each(|ns_disperse| {
239 shares
240 .iter_mut()
241 .zip(ns_disperse)
242 .for_each(|(share, ns_share)| share.0.push(ns_share))
243 });
244 Ok((commit, common, shares))
245 }
246
247 pub fn verify_share_with_verified_common(
269 common: &NsAvidmGf2Common,
270 share: &NsAvidmGf2Share,
271 ) -> VidResult<crate::VerificationResult> {
272 common.param.validate()?;
273 if !(common.ns_commits.len() == common.ns_lens.len()
274 && common.ns_commits.len() == share.num_nss()
275 && share.validate())
276 {
277 return Err(VidError::InvalidShare);
278 }
279 match common
283 .ns_commits
284 .par_iter()
285 .zip(common.ns_lens.par_iter())
286 .zip(share.0.par_iter())
287 .map(|((commit, &ns_len), content)| {
288 if !Self::ns_len_matches_shards(&common.param, ns_len, content) {
289 return Ok(Err(()));
290 }
291 AvidmGf2Scheme::verify_share(&common.param, commit, content)
292 })
293 .find_any(|r| !matches!(r, Ok(Ok(()))))
294 {
295 None => Ok(Ok(())),
296 Some(Ok(v)) => Ok(v),
297 Some(Err(e)) => Err(e),
298 }
299 }
300
301 pub fn verify_share(
303 commit: &NsAvidmGf2Commit,
304 common: &NsAvidmGf2Common,
305 share: &NsAvidmGf2Share,
306 ) -> VidResult<crate::VerificationResult> {
307 if !Self::is_consistent(commit, common) {
308 return Ok(Err(()));
309 }
310 Self::verify_share_with_verified_common(common, share)
311 }
312
313 pub fn recover(common: &NsAvidmGf2Common, shares: &[NsAvidmGf2Share]) -> VidResult<Vec<u8>> {
315 if shares.is_empty() {
316 return Err(VidError::InsufficientShares);
317 }
318 let per_ns: Vec<Vec<u8>> = (0..common.ns_lens.len())
322 .into_par_iter()
323 .map(|ns_index| Self::ns_recover(common, ns_index, shares))
324 .collect::<VidResult<Vec<_>>>()?;
325 Ok(per_ns.concat())
326 }
327
328 pub fn ns_recover(
332 common: &NsAvidmGf2Common,
333 ns_index: usize,
334 shares: &[NsAvidmGf2Share],
335 ) -> VidResult<Vec<u8>> {
336 common.param.validate()?;
337 if shares.is_empty() {
338 return Err(VidError::InsufficientShares);
339 }
340 let (Some(ns_commit), Some(&ns_len)) = (
341 common.ns_commits.get(ns_index),
342 common.ns_lens.get(ns_index),
343 ) else {
344 return Err(VidError::IndexOutOfBound);
345 };
346 if !shares.iter().all(|share| share.contains_ns(ns_index)) {
347 return Err(VidError::IndexOutOfBound);
348 }
349 let shares: Vec<_> = shares
350 .iter()
351 .filter_map(|share| share.inner_ns_share(ns_index))
352 .collect();
353 if !shares.iter().all(|share| {
356 share.payload.is_empty() || Self::ns_len_matches_shards(&common.param, ns_len, share)
357 }) {
358 return Err(VidError::InvalidShare);
359 }
360 AvidmGf2Scheme::recover(&common.param, ns_commit, &shares)
361 }
362
363 fn ns_len_matches_shards(
366 param: &NsAvidmGf2Param,
367 ns_len: usize,
368 content: &AvidmGf2Share,
369 ) -> bool {
370 content.shard_bytes().is_some_and(|shard_bytes| {
371 AvidmGf2Scheme::shard_bytes(ns_len, param.recovery_threshold) == Some(shard_bytes)
372 })
373 }
374}
375
376#[derive(Clone, Debug)]
378#[non_exhaustive]
379pub struct NsDispersal {
380 pub ns_index: usize,
382 pub payload_byte_len: usize,
384 pub commit: AvidmGf2Commit,
386 pub shares: Vec<AvidmGf2Share>,
388}
389
390#[cfg(test)]
392pub mod tests {
393 use rand::{RngCore, SeedableRng, rngs::StdRng, seq::SliceRandom};
394
395 use crate::{
396 VidError,
397 avidm_gf2::{AvidmGf2Param, namespaced::NsAvidmGf2Scheme},
398 };
399
400 fn disperse_with_payload(
401 payload: &[u8],
402 ) -> (
403 crate::avidm_gf2::namespaced::NsAvidmGf2Commit,
404 crate::avidm_gf2::namespaced::NsAvidmGf2Common,
405 Vec<crate::avidm_gf2::namespaced::NsAvidmGf2Share>,
406 ) {
407 let num_storage_nodes = 9;
408 let ns_table = [(0usize..15), (15..48)];
409
410 let mut rng = jf_utils::test_rng();
411 let weights: Vec<u32> = (0..num_storage_nodes)
412 .map(|_| rng.next_u32() % 5 + 1)
413 .collect();
414 let total_weights: u32 = weights.iter().sum();
415 let recovery_threshold = total_weights.div_ceil(3) as usize;
416 let params = NsAvidmGf2Scheme::setup(recovery_threshold, total_weights as usize).unwrap();
417
418 NsAvidmGf2Scheme::ns_disperse(¶ms, &weights, payload, ns_table.iter().cloned()).unwrap()
419 }
420
421 fn setup_test_data() -> (
422 crate::avidm_gf2::namespaced::NsAvidmGf2Commit,
423 crate::avidm_gf2::namespaced::NsAvidmGf2Common,
424 Vec<crate::avidm_gf2::namespaced::NsAvidmGf2Share>,
425 ) {
426 let payload: Vec<u8> = (0u8..48).collect();
427 disperse_with_payload(&payload)
428 }
429
430 #[test]
431 fn verify_share_with_verified_common_accepts_valid() {
432 let (commit, common, shares) = setup_test_data();
433 assert!(NsAvidmGf2Scheme::is_consistent(&commit, &common));
434 for share in &shares {
435 assert!(
436 NsAvidmGf2Scheme::verify_share_with_verified_common(&common, share)
437 .is_ok_and(|r| r.is_ok())
438 );
439 }
440 }
441
442 #[test]
443 fn verify_share_with_verified_common_rejects_tampered_share() {
444 let (_commit, common, shares) = setup_test_data();
445 let mut tampered = shares[0].clone();
447 tampered.0.pop();
448 assert!(NsAvidmGf2Scheme::verify_share_with_verified_common(&common, &tampered).is_err());
449
450 let (_commit2, _common2, shares2) = disperse_with_payload(&[0xAB; 48]);
452 let mut mixed = shares[0].clone();
453 mixed.0[0] = shares2[0].0[0].clone();
454 assert!(
455 NsAvidmGf2Scheme::verify_share_with_verified_common(&common, &mixed)
456 .is_ok_and(|r| r.is_err())
457 );
458 }
459
460 fn disperse_with_threshold(
463 recovery_threshold: usize,
464 payload: &[u8],
465 ns_table: &[std::ops::Range<usize>],
466 ) -> (
467 crate::avidm_gf2::namespaced::NsAvidmGf2Commit,
468 crate::avidm_gf2::namespaced::NsAvidmGf2Common,
469 Vec<crate::avidm_gf2::namespaced::NsAvidmGf2Share>,
470 ) {
471 let total_weights = 3 * recovery_threshold;
472 let weights = vec![1u32; total_weights];
473 let params = NsAvidmGf2Scheme::setup(recovery_threshold, total_weights).unwrap();
474 NsAvidmGf2Scheme::ns_disperse(¶ms, &weights, payload, ns_table.iter().cloned()).unwrap()
475 }
476
477 #[test]
483 fn verify_share_rejects_ns_len_outside_shard_window() {
484 let k = 4;
485 let payload = vec![7u8; 100];
486 let (commit, common, shares) = disperse_with_threshold(k, &payload, &[0..30, 30..100]);
487 assert!(NsAvidmGf2Scheme::is_consistent(&commit, &common));
488 assert_eq!(shares[0].0[0].shard_bytes(), Some(8));
491 let window = (8 - 2) * k..8 * k;
492
493 for ns_len in [window.start, window.end - 1] {
494 let mut tampered = common.clone();
495 tampered.ns_lens[0] = ns_len;
496 for share in &shares {
497 assert_eq!(
498 NsAvidmGf2Scheme::verify_share_with_verified_common(&tampered, share).unwrap(),
499 Ok(()),
500 "{ns_len}"
501 );
502 }
503 }
504 for ns_len in [0, window.start - 1, window.end, 100, usize::MAX] {
505 let mut tampered = common.clone();
506 tampered.ns_lens[0] = ns_len;
507 for share in &shares {
508 assert_eq!(
509 NsAvidmGf2Scheme::verify_share_with_verified_common(&tampered, share).unwrap(),
510 Err(()),
511 "{ns_len}"
512 );
513 }
514 }
515
516 let mut shrunk = common.clone();
521 shrunk.ns_lens[1] -= 2 * k;
522 assert!(shrunk.payload_byte_len() < common.payload_byte_len());
523 for share in &shares {
524 assert_eq!(
525 NsAvidmGf2Scheme::verify_share_with_verified_common(&shrunk, share).unwrap(),
526 Err(())
527 );
528 }
529 }
530
531 #[test]
537 fn verify_share_accepts_ns_lens_shrunk_inside_every_window() {
538 let k = 4;
539 let ns_len = 2 * k - 1;
541 let num_ns = 10;
542 let payload = vec![7u8; num_ns * ns_len];
543 let ns_table: Vec<_> = (0..num_ns).map(|i| i * ns_len..(i + 1) * ns_len).collect();
544 let (commit, common, shares) = disperse_with_threshold(k, &payload, &ns_table);
545 for share in &shares {
546 assert_eq!(share.0[0].shard_bytes(), Some(2));
547 }
548
549 let mut shrunk = common.clone();
550 shrunk.ns_lens = vec![1; num_ns];
551 assert!(NsAvidmGf2Scheme::is_consistent(&commit, &shrunk));
552 assert_eq!(shrunk.payload_byte_len(), num_ns);
553 for share in &shares {
554 assert_eq!(
555 NsAvidmGf2Scheme::verify_share_with_verified_common(&shrunk, share).unwrap(),
556 Ok(())
557 );
558 }
559 }
560
561 #[test]
562 fn composition_equivalence() {
563 let (commit, common, shares) = setup_test_data();
564 for share in &shares {
565 let full_result = NsAvidmGf2Scheme::verify_share(&commit, &common, share)
566 .unwrap()
567 .is_ok();
568 let composed_result = NsAvidmGf2Scheme::is_consistent(&commit, &common)
569 && NsAvidmGf2Scheme::verify_share_with_verified_common(&common, share)
570 .unwrap()
571 .is_ok();
572 assert_eq!(full_result, composed_result);
573 }
574 }
575
576 #[test]
577 fn is_consistent_rejects_tampered_commit() {
578 let (commit, common, _shares) = setup_test_data();
579 let (different_commit, ..) = disperse_with_payload(&[0xCD; 48]);
581 assert!(NsAvidmGf2Scheme::is_consistent(&commit, &common));
583 assert!(!NsAvidmGf2Scheme::is_consistent(&different_commit, &common));
585 }
586
587 #[test]
588 fn is_consistent_rejects_tampered_common() {
589 let (commit, common, _shares) = setup_test_data();
590 let (_, different_common, _) = disperse_with_payload(&[0xCD; 48]);
592 let mut tampered_common = common;
593 tampered_common.ns_commits = different_common.ns_commits;
594 assert!(!NsAvidmGf2Scheme::is_consistent(&commit, &tampered_common));
595 }
596
597 #[test]
598 fn round_trip() {
599 let num_storage_nodes = 9;
601 let ns_lens = [15, 33];
602 let ns_table = [(0usize..15), (15..48)];
603 let payload_byte_len = ns_lens.iter().sum();
604
605 let mut rng = jf_utils::test_rng();
606
607 let weights: Vec<u32> = (0..num_storage_nodes)
609 .map(|_| rng.next_u32() % 5 + 1)
610 .collect();
611 let total_weights: u32 = weights.iter().sum();
612 let recovery_threshold = total_weights.div_ceil(3) as usize;
613 let params = NsAvidmGf2Scheme::setup(recovery_threshold, total_weights as usize).unwrap();
614
615 println!(
616 "recovery_threshold:: {recovery_threshold} num_storage_nodes: {num_storage_nodes} \
617 payload_byte_len: {payload_byte_len}"
618 );
619 println!("weights: {weights:?}");
620
621 let payload = {
622 let mut bytes_random = vec![0u8; payload_byte_len];
623 rng.fill_bytes(&mut bytes_random);
624 bytes_random
625 };
626
627 let (commit, common, mut shares) =
628 NsAvidmGf2Scheme::ns_disperse(¶ms, &weights, &payload, ns_table.iter().cloned())
629 .unwrap();
630
631 assert_eq!(shares.len(), num_storage_nodes);
632
633 assert_eq!(
634 commit,
635 NsAvidmGf2Scheme::commit(¶ms, &payload, ns_table.iter().cloned())
636 .unwrap()
637 .0
638 );
639
640 shares.iter().for_each(|share| {
642 assert!(
643 NsAvidmGf2Scheme::verify_share(&commit, &common, share).is_ok_and(|r| r.is_ok())
644 )
645 });
646
647 shares.shuffle(&mut rng);
649 let mut cumulated_weights = 0;
650 let mut cut_index = 0;
651 while cumulated_weights <= recovery_threshold {
652 cumulated_weights += shares[cut_index].weight();
653 cut_index += 1;
654 }
655 let ns0_payload_recovered =
656 NsAvidmGf2Scheme::ns_recover(&common, 0, &shares[..cut_index]).unwrap();
657 assert_eq!(ns0_payload_recovered[..], payload[ns_table[0].clone()]);
658 let ns1_payload_recovered =
659 NsAvidmGf2Scheme::ns_recover(&common, 1, &shares[..cut_index]).unwrap();
660 assert_eq!(ns1_payload_recovered[..], payload[ns_table[1].clone()]);
661 let payload_recovered = NsAvidmGf2Scheme::recover(&common, &shares[..cut_index]).unwrap();
662 assert_eq!(payload_recovered, payload);
663 }
664
665 proptest::proptest! {
666 #[test]
667 fn any_threshold_covering_subset_recovers_the_payload(
668 weights in proptest::collection::vec(1u32..=5, 1..=9),
669 payload in proptest::collection::vec(proptest::num::u8::ANY, 0..2048),
670 ns_ends in proptest::collection::vec(proptest::num::usize::ANY, 0..4),
671 shuffle_seed: u64,
672 ) {
673 let total_weights: u32 = weights.iter().sum();
674 let recovery_threshold = total_weights.div_ceil(3) as usize;
675 let params =
676 NsAvidmGf2Scheme::setup(recovery_threshold, total_weights as usize).unwrap();
677 let mut ends: Vec<usize> = ns_ends
678 .iter()
679 .map(|end| end % (payload.len() + 1))
680 .chain([payload.len()])
681 .collect();
682 ends.sort_unstable();
683 ends.dedup();
684 let ns_table: Vec<_> = std::iter::once(0)
685 .chain(ends.iter().copied())
686 .zip(ends.iter().copied())
687 .map(|(start, end)| start..end)
688 .filter(|range| !range.is_empty() || payload.is_empty())
689 .collect();
690
691 let (_commit, common, mut shares) =
692 NsAvidmGf2Scheme::ns_disperse(¶ms, &weights, &payload, ns_table.clone())
693 .unwrap();
694 shares.shuffle(&mut StdRng::seed_from_u64(shuffle_seed));
695 let mut weight = 0;
696 let subset: Vec<_> = shares
697 .into_iter()
698 .take_while(|share| {
699 let needed = weight < recovery_threshold;
700 weight += share.weight();
701 needed
702 })
703 .collect();
704
705 proptest::prop_assert_eq!(NsAvidmGf2Scheme::recover(&common, &subset).unwrap(), payload.clone());
706 for (ns_index, range) in ns_table.into_iter().enumerate() {
707 proptest::prop_assert_eq!(
708 NsAvidmGf2Scheme::ns_recover(&common, ns_index, &subset).unwrap(),
709 payload[range].to_vec()
710 );
711 }
712 }
713 }
714
715 #[test]
716 fn ns_recover_rejects_ns_len_the_shards_were_not_dispersed_from() {
717 let k = 4;
718 let (_commit, common, shares) = disperse_with_threshold(k, &[7u8; 100], &[0..30, 30..100]);
719 let mut shrunk = common.clone();
720 shrunk.ns_lens[1] -= 2 * k;
721 assert!(matches!(
722 NsAvidmGf2Scheme::ns_recover(&shrunk, 1, &shares),
723 Err(VidError::InvalidShare)
724 ));
725 }
726
727 #[test]
728 fn ns_recover_rejects_common_with_fewer_commits_than_lens() {
729 let (_commit, mut common, shares) = setup_test_data();
730 common.ns_commits.pop();
731 assert!(matches!(
732 NsAvidmGf2Scheme::ns_recover(&common, 1, &shares),
733 Err(VidError::IndexOutOfBound)
734 ));
735 }
736
737 #[test]
738 fn forged_common_param_is_rejected() {
739 let (_commit, common, shares) = setup_test_data();
740 for (recovery_threshold, total_weights) in [(0, 4), (1, 1 << 16)] {
741 let mut forged = common.clone();
742 forged.param = AvidmGf2Param {
743 total_weights,
744 recovery_threshold,
745 };
746 assert!(
747 matches!(
748 NsAvidmGf2Scheme::verify_share_with_verified_common(&forged, &shares[0]),
749 Err(VidError::InvalidParam)
750 ),
751 "verify_share_with_verified_common {:?}",
752 forged.param
753 );
754 assert!(
755 matches!(
756 NsAvidmGf2Scheme::ns_recover(&forged, 0, &shares),
757 Err(VidError::InvalidParam)
758 ),
759 "ns_recover {:?}",
760 forged.param
761 );
762 }
763 }
764
765 #[test]
769 fn aggregate_commit_matches_commit() {
770 let num_storage_nodes = 9;
771 let ns_table = [(0usize..15), (15..48), (48..49)];
772 let weights = vec![1u32; num_storage_nodes];
773 let params = NsAvidmGf2Scheme::setup(3, num_storage_nodes).unwrap();
774 let payload: Vec<u8> = (0..49).map(|i| i as u8).collect();
775
776 let ns_commits: Vec<_> = ns_table
777 .iter()
778 .enumerate()
779 .map(|(ns_index, range)| {
780 NsAvidmGf2Scheme::ns_disperse_one(
781 ¶ms,
782 &weights,
783 &payload[range.clone()],
784 ns_index,
785 )
786 .unwrap()
787 .commit
788 })
789 .collect();
790 let aggregated = NsAvidmGf2Scheme::aggregate_commit(&ns_commits).unwrap();
791
792 let (expected, _) =
793 NsAvidmGf2Scheme::commit(¶ms, &payload, ns_table.iter().cloned()).unwrap();
794 assert_eq!(aggregated, expected);
795 }
796}