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 inner_ns_share(&self, ns_index: usize) -> Option<AvidmGf2Share> {
71 self.0.get(ns_index).cloned()
72 }
73
74 pub fn range(&self) -> Option<&Range<usize>> {
77 let first = self.0.first()?.range();
78 self.0
79 .iter()
80 .all(|share| share.range() == first)
81 .then_some(first)
82 }
83}
84
85impl From<Vec<AvidmGf2Share>> for NsAvidmGf2Share {
86 fn from(ns_shares: Vec<AvidmGf2Share>) -> Self {
87 Self(ns_shares)
88 }
89}
90
91impl NsAvidmGf2Scheme {
92 pub fn setup(recovery_threshold: usize, total_weights: usize) -> VidResult<NsAvidmGf2Param> {
94 NsAvidmGf2Param::new(recovery_threshold, total_weights)
95 }
96
97 pub fn commit(
101 param: &NsAvidmGf2Param,
102 payload: &[u8],
103 ns_table: impl IntoIterator<Item = Range<usize>>,
104 ) -> VidResult<(NsAvidmGf2Commit, NsAvidmGf2Common)> {
105 let ns_table = ns_table.into_iter().collect::<Vec<_>>();
106 let ns_lens = ns_table.iter().map(|r| r.len()).collect::<Vec<_>>();
107 let ns_commits = ns_table
108 .into_iter()
109 .map(|ns_range| AvidmGf2Scheme::commit(param, &payload[ns_range]))
110 .collect::<Result<Vec<_>, _>>()?;
111 let common = NsAvidmGf2Common {
112 param: param.clone(),
113 ns_commits,
114 ns_lens,
115 };
116 let commit = MerkleTree::from_elems(None, common.ns_commits.iter().map(|c| c.commit))
117 .map_err(|err| VidError::Internal(err.into()))?
118 .commitment();
119 Ok((NsAvidmGf2Commit { commit }, common))
120 }
121
122 pub fn is_consistent(commit: &NsAvidmGf2Commit, common: &NsAvidmGf2Common) -> bool {
124 let Ok(mt) =
125 MerkleTree::from_elems(None, common.ns_commits.iter().map(|commit| commit.commit))
126 else {
127 return false;
128 };
129 commit.commit == mt.commitment()
130 }
131
132 pub fn ns_disperse(
137 param: &NsAvidmGf2Param,
138 distribution: &[u32],
139 payload: &[u8],
140 ns_table: impl IntoIterator<Item = Range<usize>>,
141 ) -> VidResult<(NsAvidmGf2Commit, NsAvidmGf2Common, Vec<NsAvidmGf2Share>)> {
142 let num_storage_nodes = distribution.len();
143 let ns_ranges: Vec<Range<usize>> = ns_table.into_iter().collect();
144 let ns_lens: Vec<usize> = ns_ranges.iter().map(|r| r.len()).collect();
145
146 let per_ns: Vec<(AvidmGf2Commit, Vec<AvidmGf2Share>)> = ns_ranges
151 .par_iter()
152 .map(|ns_range| {
153 AvidmGf2Scheme::disperse(param, distribution, &payload[ns_range.clone()])
154 })
155 .collect::<VidResult<Vec<_>>>()?;
156
157 let (ns_commits, disperses): (Vec<_>, Vec<_>) = per_ns.into_iter().unzip();
158
159 let common = NsAvidmGf2Common {
160 param: param.clone(),
161 ns_commits,
162 ns_lens,
163 };
164 let commit = NsAvidmGf2Commit {
165 commit: MerkleTree::from_elems(None, common.ns_commits.iter().map(|c| c.commit))
166 .map_err(|err| VidError::Internal(err.into()))?
167 .commitment(),
168 };
169 let mut shares = vec![NsAvidmGf2Share::default(); num_storage_nodes];
170 disperses.into_iter().for_each(|ns_disperse| {
171 shares
172 .iter_mut()
173 .zip(ns_disperse)
174 .for_each(|(share, ns_share)| share.0.push(ns_share))
175 });
176 Ok((commit, common, shares))
177 }
178
179 pub fn ns_disperse_one(
184 param: &NsAvidmGf2Param,
185 distribution: &[u32],
186 ns_payload: &[u8],
187 ns_index: usize,
188 ) -> VidResult<NsDispersal> {
189 let payload_byte_len = ns_payload.len();
190 let (commit, shares) = AvidmGf2Scheme::disperse(param, distribution, ns_payload)?;
191 Ok(NsDispersal {
192 ns_index,
193 payload_byte_len,
194 commit,
195 shares,
196 })
197 }
198
199 #[cfg(any(test, feature = "testing"))]
205 pub fn ns_disperse_non_codeword(
206 param: &NsAvidmGf2Param,
207 distribution: &[u32],
208 payload: &[u8],
209 ns_table: impl IntoIterator<Item = Range<usize>>,
210 ) -> VidResult<(NsAvidmGf2Commit, NsAvidmGf2Common, Vec<NsAvidmGf2Share>)> {
211 let num_storage_nodes = distribution.len();
212 let ns_ranges: Vec<Range<usize>> = ns_table.into_iter().collect();
213 let ns_lens: Vec<usize> = ns_ranges.iter().map(|r| r.len()).collect();
214 let per_ns: Vec<(AvidmGf2Commit, Vec<AvidmGf2Share>)> = ns_ranges
215 .iter()
216 .map(|ns_range| {
217 AvidmGf2Scheme::disperse_non_codeword(
218 param,
219 distribution,
220 &payload[ns_range.clone()],
221 )
222 })
223 .collect::<VidResult<Vec<_>>>()?;
224 let (ns_commits, disperses): (Vec<_>, Vec<_>) = per_ns.into_iter().unzip();
225 let common = NsAvidmGf2Common {
226 param: param.clone(),
227 ns_commits,
228 ns_lens,
229 };
230 let commit = NsAvidmGf2Commit {
231 commit: MerkleTree::from_elems(None, common.ns_commits.iter().map(|c| c.commit))
232 .map_err(|err| VidError::Internal(err.into()))?
233 .commitment(),
234 };
235 let mut shares = vec![NsAvidmGf2Share::default(); num_storage_nodes];
236 disperses.into_iter().for_each(|ns_disperse| {
237 shares
238 .iter_mut()
239 .zip(ns_disperse)
240 .for_each(|(share, ns_share)| share.0.push(ns_share))
241 });
242 Ok((commit, common, shares))
243 }
244
245 pub fn verify_share_with_verified_common(
252 common: &NsAvidmGf2Common,
253 share: &NsAvidmGf2Share,
254 ) -> VidResult<crate::VerificationResult> {
255 if !(common.ns_commits.len() == common.ns_lens.len()
256 && common.ns_commits.len() == share.num_nss()
257 && share.validate())
258 {
259 return Err(VidError::InvalidShare);
260 }
261 match common
265 .ns_commits
266 .par_iter()
267 .zip(share.0.par_iter())
268 .map(|(commit, content)| AvidmGf2Scheme::verify_share(&common.param, commit, content))
269 .find_any(|r| !matches!(r, Ok(Ok(()))))
270 {
271 None => Ok(Ok(())),
272 Some(Ok(v)) => Ok(v),
273 Some(Err(e)) => Err(e),
274 }
275 }
276
277 pub fn verify_share(
279 commit: &NsAvidmGf2Commit,
280 common: &NsAvidmGf2Common,
281 share: &NsAvidmGf2Share,
282 ) -> VidResult<crate::VerificationResult> {
283 if !Self::is_consistent(commit, common) {
284 return Ok(Err(()));
285 }
286 Self::verify_share_with_verified_common(common, share)
287 }
288
289 pub fn recover(common: &NsAvidmGf2Common, shares: &[NsAvidmGf2Share]) -> VidResult<Vec<u8>> {
291 if shares.is_empty() {
292 return Err(VidError::InsufficientShares);
293 }
294 let per_ns: Vec<Vec<u8>> = (0..common.ns_lens.len())
298 .into_par_iter()
299 .map(|ns_index| Self::ns_recover(common, ns_index, shares))
300 .collect::<VidResult<Vec<_>>>()?;
301 Ok(per_ns.concat())
302 }
303
304 pub fn ns_recover(
308 common: &NsAvidmGf2Common,
309 ns_index: usize,
310 shares: &[NsAvidmGf2Share],
311 ) -> VidResult<Vec<u8>> {
312 if shares.is_empty() {
313 return Err(VidError::InsufficientShares);
314 }
315 if ns_index >= common.ns_lens.len()
316 || !shares.iter().all(|share| share.contains_ns(ns_index))
317 {
318 return Err(VidError::IndexOutOfBound);
319 }
320 let ns_commit = &common.ns_commits[ns_index];
321 let shares: Vec<_> = shares
322 .iter()
323 .filter_map(|share| share.inner_ns_share(ns_index))
324 .collect();
325 AvidmGf2Scheme::recover(&common.param, ns_commit, &shares)
326 }
327}
328
329#[derive(Clone, Debug)]
331#[non_exhaustive]
332pub struct NsDispersal {
333 pub ns_index: usize,
335 pub payload_byte_len: usize,
337 pub commit: AvidmGf2Commit,
339 pub shares: Vec<AvidmGf2Share>,
341}
342
343#[cfg(test)]
345pub mod tests {
346 use rand::{RngCore, seq::SliceRandom};
347
348 use crate::avidm_gf2::namespaced::NsAvidmGf2Scheme;
349
350 fn disperse_with_payload(
351 payload: &[u8],
352 ) -> (
353 crate::avidm_gf2::namespaced::NsAvidmGf2Commit,
354 crate::avidm_gf2::namespaced::NsAvidmGf2Common,
355 Vec<crate::avidm_gf2::namespaced::NsAvidmGf2Share>,
356 ) {
357 let num_storage_nodes = 9;
358 let ns_table = [(0usize..15), (15..48)];
359
360 let mut rng = jf_utils::test_rng();
361 let weights: Vec<u32> = (0..num_storage_nodes)
362 .map(|_| rng.next_u32() % 5 + 1)
363 .collect();
364 let total_weights: u32 = weights.iter().sum();
365 let recovery_threshold = total_weights.div_ceil(3) as usize;
366 let params = NsAvidmGf2Scheme::setup(recovery_threshold, total_weights as usize).unwrap();
367
368 NsAvidmGf2Scheme::ns_disperse(¶ms, &weights, payload, ns_table.iter().cloned()).unwrap()
369 }
370
371 fn setup_test_data() -> (
372 crate::avidm_gf2::namespaced::NsAvidmGf2Commit,
373 crate::avidm_gf2::namespaced::NsAvidmGf2Common,
374 Vec<crate::avidm_gf2::namespaced::NsAvidmGf2Share>,
375 ) {
376 let payload: Vec<u8> = (0u8..48).collect();
377 disperse_with_payload(&payload)
378 }
379
380 #[test]
381 fn verify_share_with_verified_common_accepts_valid() {
382 let (commit, common, shares) = setup_test_data();
383 assert!(NsAvidmGf2Scheme::is_consistent(&commit, &common));
384 for share in &shares {
385 assert!(
386 NsAvidmGf2Scheme::verify_share_with_verified_common(&common, share)
387 .is_ok_and(|r| r.is_ok())
388 );
389 }
390 }
391
392 #[test]
393 fn verify_share_with_verified_common_rejects_tampered_share() {
394 let (_commit, common, shares) = setup_test_data();
395 let mut tampered = shares[0].clone();
397 tampered.0.pop();
398 assert!(NsAvidmGf2Scheme::verify_share_with_verified_common(&common, &tampered).is_err());
399
400 let (_commit2, _common2, shares2) = disperse_with_payload(&[0xAB; 48]);
402 let mut mixed = shares[0].clone();
403 mixed.0[0] = shares2[0].0[0].clone();
404 assert!(
405 NsAvidmGf2Scheme::verify_share_with_verified_common(&common, &mixed)
406 .is_ok_and(|r| r.is_err())
407 );
408 }
409
410 #[test]
411 fn composition_equivalence() {
412 let (commit, common, shares) = setup_test_data();
413 for share in &shares {
414 let full_result = NsAvidmGf2Scheme::verify_share(&commit, &common, share)
415 .unwrap()
416 .is_ok();
417 let composed_result = NsAvidmGf2Scheme::is_consistent(&commit, &common)
418 && NsAvidmGf2Scheme::verify_share_with_verified_common(&common, share)
419 .unwrap()
420 .is_ok();
421 assert_eq!(full_result, composed_result);
422 }
423 }
424
425 #[test]
426 fn is_consistent_rejects_tampered_commit() {
427 let (commit, common, _shares) = setup_test_data();
428 let (different_commit, ..) = disperse_with_payload(&[0xCD; 48]);
430 assert!(NsAvidmGf2Scheme::is_consistent(&commit, &common));
432 assert!(!NsAvidmGf2Scheme::is_consistent(&different_commit, &common));
434 }
435
436 #[test]
437 fn is_consistent_rejects_tampered_common() {
438 let (commit, common, _shares) = setup_test_data();
439 let (_, different_common, _) = disperse_with_payload(&[0xCD; 48]);
441 let mut tampered_common = common;
442 tampered_common.ns_commits = different_common.ns_commits;
443 assert!(!NsAvidmGf2Scheme::is_consistent(&commit, &tampered_common));
444 }
445
446 #[test]
447 fn round_trip() {
448 let num_storage_nodes = 9;
450 let ns_lens = [15, 33];
451 let ns_table = [(0usize..15), (15..48)];
452 let payload_byte_len = ns_lens.iter().sum();
453
454 let mut rng = jf_utils::test_rng();
455
456 let weights: Vec<u32> = (0..num_storage_nodes)
458 .map(|_| rng.next_u32() % 5 + 1)
459 .collect();
460 let total_weights: u32 = weights.iter().sum();
461 let recovery_threshold = total_weights.div_ceil(3) as usize;
462 let params = NsAvidmGf2Scheme::setup(recovery_threshold, total_weights as usize).unwrap();
463
464 println!(
465 "recovery_threshold:: {recovery_threshold} num_storage_nodes: {num_storage_nodes} \
466 payload_byte_len: {payload_byte_len}"
467 );
468 println!("weights: {weights:?}");
469
470 let payload = {
471 let mut bytes_random = vec![0u8; payload_byte_len];
472 rng.fill_bytes(&mut bytes_random);
473 bytes_random
474 };
475
476 let (commit, common, mut shares) =
477 NsAvidmGf2Scheme::ns_disperse(¶ms, &weights, &payload, ns_table.iter().cloned())
478 .unwrap();
479
480 assert_eq!(shares.len(), num_storage_nodes);
481
482 assert_eq!(
483 commit,
484 NsAvidmGf2Scheme::commit(¶ms, &payload, ns_table.iter().cloned())
485 .unwrap()
486 .0
487 );
488
489 shares.iter().for_each(|share| {
491 assert!(
492 NsAvidmGf2Scheme::verify_share(&commit, &common, share).is_ok_and(|r| r.is_ok())
493 )
494 });
495
496 shares.shuffle(&mut rng);
498 let mut cumulated_weights = 0;
499 let mut cut_index = 0;
500 while cumulated_weights <= recovery_threshold {
501 cumulated_weights += shares[cut_index].weight();
502 cut_index += 1;
503 }
504 let ns0_payload_recovered =
505 NsAvidmGf2Scheme::ns_recover(&common, 0, &shares[..cut_index]).unwrap();
506 assert_eq!(ns0_payload_recovered[..], payload[ns_table[0].clone()]);
507 let ns1_payload_recovered =
508 NsAvidmGf2Scheme::ns_recover(&common, 1, &shares[..cut_index]).unwrap();
509 assert_eq!(ns1_payload_recovered[..], payload[ns_table[1].clone()]);
510 let payload_recovered = NsAvidmGf2Scheme::recover(&common, &shares[..cut_index]).unwrap();
511 assert_eq!(payload_recovered, payload);
512 }
513}