1use std::ops::Range;
4
5use jf_merkle_tree::MerkleTreeScheme;
6use serde::{Deserialize, Serialize};
7
8use super::{AvidMCommit, AvidMShare, RawAvidMShare};
9use crate::{
10 avid_m::{AvidMScheme, MerkleTree},
11 VidError, VidResult, VidScheme,
12};
13
14pub struct NsAvidMScheme;
16
17pub type NsAvidMCommit = super::AvidMCommit;
19pub type NsAvidMParam = super::AvidMParam;
21
22#[derive(Clone, Debug, Hash, Serialize, Deserialize, Eq, PartialEq, Default)]
24pub struct NsAvidMShare {
25 pub(crate) index: u32,
27 pub(crate) ns_commits: Vec<AvidMCommit>,
29 pub(crate) ns_lens: Vec<usize>,
31 pub(crate) content: Vec<RawAvidMShare>,
33}
34
35impl NsAvidMShare {
36 pub fn num_nss(&self) -> usize {
39 self.ns_commits.len()
40 }
41
42 pub fn inner_ns_share(&self, ns_index: usize) -> Option<AvidMShare> {
44 if ns_index >= self.ns_lens.len() || ns_index >= self.content.len() {
45 return None;
46 }
47 Some(AvidMShare {
48 index: self.index,
49 payload_byte_len: self.ns_lens[ns_index],
50 content: self.content[ns_index].clone(),
51 })
52 }
53
54 pub fn payload_byte_len(&self) -> usize {
56 self.ns_lens.iter().sum()
57 }
58
59 pub fn contains_ns(&self, ns_index: usize) -> bool {
61 self.ns_commits.len() > ns_index
62 && self.ns_lens.len() > ns_index
63 && self.content.len() > ns_index
64 }
65
66 pub fn ns_commits(&self) -> &[AvidMCommit] {
68 &self.ns_commits
69 }
70
71 pub fn ns_lens(&self) -> &[usize] {
73 &self.ns_lens
74 }
75
76 pub fn ns_commit(&self, ns_index: usize) -> &AvidMCommit {
79 &self.ns_commits[ns_index]
80 }
81
82 pub fn ns_len(&self, ns_index: usize) -> usize {
85 self.ns_lens[ns_index]
86 }
87}
88
89impl NsAvidMScheme {
90 pub fn setup(recovery_threshold: usize, total_weights: usize) -> VidResult<NsAvidMParam> {
92 NsAvidMParam::new(recovery_threshold, total_weights)
93 }
94
95 pub fn commit(
99 param: &NsAvidMParam,
100 payload: &[u8],
101 ns_table: impl IntoIterator<Item = Range<usize>>,
102 ) -> VidResult<NsAvidMCommit> {
103 let ns_commits = ns_table
104 .into_iter()
105 .map(|ns_range| {
106 AvidMScheme::commit(param, &payload[ns_range]).map(|commit| commit.commit)
107 })
108 .collect::<Result<Vec<_>, _>>()?;
109 Ok(NsAvidMCommit {
110 commit: MerkleTree::from_elems(None, &ns_commits)
111 .map_err(|err| VidError::Internal(err.into()))?
112 .commitment(),
113 })
114 }
115
116 pub fn ns_disperse(
121 param: &NsAvidMParam,
122 distribution: &[u32],
123 payload: &[u8],
124 ns_table: impl IntoIterator<Item = Range<usize>>,
125 ) -> VidResult<(NsAvidMCommit, Vec<NsAvidMShare>)> {
126 let mut ns_commits = vec![];
127 let mut disperses = vec![];
128 let mut ns_lens = vec![];
129 for ns_range in ns_table {
130 ns_lens.push(ns_range.len());
131 let (commit, shares) = AvidMScheme::disperse(param, distribution, &payload[ns_range])?;
132 ns_commits.push(commit.commit);
133 disperses.push(shares);
134 }
135 let commit = NsAvidMCommit {
136 commit: MerkleTree::from_elems(None, &ns_commits)
137 .map_err(|err| VidError::Internal(err.into()))?
138 .commitment(),
139 };
140 let ns_commits: Vec<_> = ns_commits
141 .into_iter()
142 .map(|comm| AvidMCommit { commit: comm })
143 .collect();
144 let mut shares = vec![NsAvidMShare::default(); disperses[0].len()];
145 shares.iter_mut().for_each(|share| {
146 share.index = disperses[0][0].index;
147 share.ns_commits = ns_commits.clone();
148 share.ns_lens = ns_lens.clone();
149 });
150 disperses.into_iter().for_each(|ns_disperse| {
151 shares
152 .iter_mut()
153 .zip(ns_disperse)
154 .for_each(|(share, ns_share)| share.content.push(ns_share.content))
155 });
156 Ok((commit, shares))
157 }
158
159 pub fn verify_share(
161 param: &NsAvidMParam,
162 commit: &NsAvidMCommit,
163 share: &NsAvidMShare,
164 ) -> VidResult<crate::VerificationResult> {
165 if !(share.ns_commits.len() == share.ns_lens.len()
166 && share.ns_commits.len() == share.content.len())
167 {
168 return Err(VidError::InvalidShare);
169 }
170 for (commit, content) in share.ns_commits.iter().zip(share.content.iter()) {
172 if AvidMScheme::verify_internal(param, commit, content)?.is_err() {
173 return Ok(Err(()));
174 }
175 }
176 let expected_commit = NsAvidMCommit {
178 commit: MerkleTree::from_elems(
179 None,
180 share.ns_commits.iter().map(|commit| commit.commit),
181 )
182 .map_err(|err| VidError::Internal(err.into()))?
183 .commitment(),
184 };
185 Ok(if &expected_commit == commit {
186 Ok(())
187 } else {
188 Err(())
189 })
190 }
191
192 pub fn recover(param: &NsAvidMParam, shares: &[NsAvidMShare]) -> VidResult<Vec<u8>> {
194 if shares.is_empty() {
195 return Err(VidError::InsufficientShares);
196 }
197 let mut result = vec![];
198 for ns_index in 0..shares[0].ns_lens.len() {
199 result.append(&mut Self::ns_recover(param, ns_index, shares)?)
200 }
201 Ok(result)
202 }
203
204 pub fn ns_recover(
208 param: &NsAvidMParam,
209 ns_index: usize,
210 shares: &[NsAvidMShare],
211 ) -> VidResult<Vec<u8>> {
212 if shares.is_empty() {
213 return Err(VidError::InsufficientShares);
214 }
215 if shares
216 .iter()
217 .any(|share| ns_index >= share.ns_lens.len() || ns_index >= share.content.len())
218 {
219 return Err(VidError::IndexOutOfBound);
220 }
221 let ns_commit = shares[0].ns_commits[ns_index];
222 let shares: Vec<_> = shares
223 .iter()
224 .filter_map(|share| share.inner_ns_share(ns_index))
225 .collect();
226 AvidMScheme::recover(param, &ns_commit, &shares)
227 }
228}
229
230#[cfg(test)]
232pub mod tests {
233 use rand::{seq::SliceRandom, RngCore};
234
235 use crate::avid_m::namespaced::NsAvidMScheme;
236
237 #[test]
238 fn round_trip() {
239 let num_storage_nodes = 9;
241 let recovery_threshold = 3;
242 let ns_lens = [15, 33];
243 let ns_table = [(0usize..15), (15..48)];
244 let payload_byte_len = ns_lens.iter().sum();
245
246 let mut rng = jf_utils::test_rng();
249
250 let weights: Vec<u32> = (0..num_storage_nodes)
251 .map(|_| rng.next_u32() % 5 + 1)
252 .collect();
253 let total_weights: u32 = weights.iter().sum();
254 let params = NsAvidMScheme::setup(recovery_threshold, total_weights as usize).unwrap();
255
256 println!(
257 "recovery_threshold:: {recovery_threshold} num_storage_nodes: {num_storage_nodes} payload_byte_len: {payload_byte_len}"
258 );
259 println!("weights: {weights:?}");
260
261 let payload = {
262 let mut bytes_random = vec![0u8; payload_byte_len];
263 rng.fill_bytes(&mut bytes_random);
264 bytes_random
265 };
266
267 let (commit, mut shares) =
268 NsAvidMScheme::ns_disperse(¶ms, &weights, &payload, ns_table.iter().cloned())
269 .unwrap();
270
271 assert_eq!(shares.len(), num_storage_nodes);
272
273 assert_eq!(
274 commit,
275 NsAvidMScheme::commit(¶ms, &payload, ns_table.iter().cloned()).unwrap()
276 );
277
278 shares.iter().for_each(|share| {
280 assert!(NsAvidMScheme::verify_share(¶ms, &commit, share).is_ok_and(|r| r.is_ok()))
281 });
282
283 shares.shuffle(&mut rng);
285 let mut cumulated_weights = 0;
286 let mut cut_index = 0;
287 while cumulated_weights <= recovery_threshold {
288 cumulated_weights += shares[cut_index].content[0].range.len();
289 cut_index += 1;
290 }
291 let ns0_payload_recovered =
292 NsAvidMScheme::ns_recover(¶ms, 0, &shares[..cut_index]).unwrap();
293 assert_eq!(ns0_payload_recovered[..], payload[ns_table[0].clone()]);
294 let ns1_payload_recovered =
295 NsAvidMScheme::ns_recover(¶ms, 1, &shares[..cut_index]).unwrap();
296 assert_eq!(ns1_payload_recovered[..], payload[ns_table[1].clone()]);
297 let payload_recovered = NsAvidMScheme::recover(¶ms, &shares[..cut_index]).unwrap();
298 assert_eq!(payload_recovered, payload);
299 }
300}