1use std::{collections::HashSet, ops::Range};
4
5use jf_merkle_tree::MerkleTreeScheme;
6use jf_utils::canonical;
7use serde::{Deserialize, Serialize};
8
9use crate::{
10 VerificationResult, VidError, VidResult, VidScheme,
11 avidm::{
12 AvidMCommit, AvidMParam, AvidMScheme, AvidMShare, Config, F, MerkleProof, MerkleTree,
13 config::AvidMConfig,
14 namespaced::{NsAvidMCommit, NsAvidMScheme, NsAvidMShare},
15 },
16};
17
18#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
31pub struct AvidMBadEncodingProof {
32 #[serde(with = "canonical")]
34 recovered_poly: Vec<F>,
35 #[serde(with = "canonical")]
37 raw_shares: Vec<(usize, MerkleProof)>,
38}
39
40impl AvidMScheme {
41 pub fn proof_of_incorrect_encoding(
44 param: &AvidMParam,
45 commit: &AvidMCommit,
46 shares: &[AvidMShare],
47 ) -> VidResult<AvidMBadEncodingProof> {
48 let shares = shares
50 .iter()
51 .filter(|share| {
52 AvidMScheme::verify_share(param, commit, share).is_ok_and(|r| r.is_ok())
53 })
54 .cloned()
55 .collect::<Vec<_>>();
56 let witness = Self::recover_fields(param, &shares)?;
59 let (mt, _) = Self::raw_encode(param, &witness)?;
60 if mt.commitment() == commit.commit {
61 return Err(VidError::Argument(
62 "Cannot generate the proof of incorrect encoding: encoding is good.".to_string(),
63 ));
64 }
65
66 let mut raw_shares = vec![];
67 let mut visited_indices = HashSet::new();
68 for share in shares {
69 for (index, mt_proof) in share
70 .content
71 .range
72 .clone()
73 .zip(share.content.mt_proofs.iter())
74 {
75 if index >= param.total_weights {
76 return Err(VidError::InvalidShare);
77 }
78 if visited_indices.contains(&index) {
79 return Err(VidError::InvalidShare);
80 }
81 raw_shares.push((index, mt_proof.clone()));
82 visited_indices.insert(index);
83 if raw_shares.len() == param.recovery_threshold {
84 break;
85 }
86 }
87 if raw_shares.len() == param.recovery_threshold {
88 break;
89 }
90 }
91 if raw_shares.len() != param.recovery_threshold {
92 return Err(VidError::InsufficientShares);
93 }
94
95 Ok(AvidMBadEncodingProof {
96 recovered_poly: witness,
97 raw_shares,
98 })
99 }
100}
101
102impl AvidMBadEncodingProof {
103 pub fn verify(
105 &self,
106 param: &AvidMParam,
107 commit: &AvidMCommit,
108 ) -> VidResult<VerificationResult> {
109 param.validate()?;
110 if self.raw_shares.len() != param.recovery_threshold {
112 return Err(VidError::InvalidParam);
113 }
114 if self.recovered_poly.len() > param.recovery_threshold {
115 return Err(VidError::InvalidParam);
117 }
118 let (mt, raw_shares) = AvidMScheme::raw_encode(param, &self.recovered_poly)?;
119 if mt.commitment() == commit.commit {
120 return Ok(Err(()));
121 }
122 let mut visited_indices = HashSet::new();
123 for (index, proof) in self.raw_shares.iter() {
124 if *index >= param.total_weights || visited_indices.contains(index) {
125 return Err(VidError::InvalidShare);
126 }
127 let digest = Config::raw_share_digest(&raw_shares[*index])?;
128 if MerkleTree::verify(&commit.commit, *index as u64, &digest, proof)?.is_err() {
129 return Ok(Err(()));
130 }
131 visited_indices.insert(*index);
132 }
133 Ok(Ok(()))
134 }
135}
136
137#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
141pub struct NsAvidMBadEncodingProof {
142 pub ns_index: usize,
144 pub ns_commit: AvidMCommit,
146 pub ns_mt_proof: MerkleProof,
148 pub ns_proof: AvidMBadEncodingProof,
150}
151
152impl NsAvidMScheme {
153 pub fn proof_of_incorrect_encoding_for_namespace(
155 param: &AvidMParam,
156 ns_index: usize,
157 commit: &NsAvidMCommit,
158 shares: &[NsAvidMShare],
159 ) -> VidResult<NsAvidMBadEncodingProof> {
160 if shares.is_empty() {
161 return Err(VidError::InsufficientShares);
162 }
163 if shares.iter().any(|share| !share.contains_ns(ns_index)) {
164 return Err(VidError::IndexOutOfBound);
165 }
166 let mt = MerkleTree::from_elems(
167 None,
168 shares[0].ns_commits().iter().map(|commit| commit.commit),
169 )?;
170 if mt.commitment() != commit.commit {
171 return Err(VidError::InvalidParam);
172 }
173 let (ns_commit, ns_mt_proof) = mt
174 .lookup(ns_index as u64)
175 .expect_ok()
176 .expect("MT lookup shouldn't fail");
177 let ns_commit = AvidMCommit { commit: *ns_commit };
178 let shares = shares
179 .iter()
180 .filter_map(|share| share.inner_ns_share(ns_index))
181 .collect::<Vec<_>>();
182 Ok(NsAvidMBadEncodingProof {
183 ns_index,
184 ns_commit,
185 ns_mt_proof,
186 ns_proof: AvidMScheme::proof_of_incorrect_encoding(param, &ns_commit, &shares)?,
187 })
188 }
189
190 pub fn proof_of_incorrect_encoding(
192 param: &AvidMParam,
193 commit: &NsAvidMCommit,
194 shares: &[NsAvidMShare],
195 ) -> VidResult<NsAvidMBadEncodingProof> {
196 if shares.is_empty() {
197 return Err(VidError::InsufficientShares);
198 }
199 for ns_index in 0..shares[0].ns_commits().len() {
200 let result =
201 Self::proof_of_incorrect_encoding_for_namespace(param, ns_index, commit, shares);
202 if matches!(
204 result,
205 Ok(_)
206 | Err(VidError::InvalidShare)
207 | Err(VidError::IndexOutOfBound)
208 | Err(VidError::InsufficientShares)
209 ) {
210 return result;
211 }
212 }
213 Err(VidError::InvalidParam)
214 }
215}
216
217impl NsAvidMBadEncodingProof {
218 pub fn verify(
220 &self,
221 param: &AvidMParam,
222 commit: &NsAvidMCommit,
223 ) -> VidResult<VerificationResult> {
224 if MerkleTree::verify(
225 &commit.commit,
226 self.ns_index as u64,
227 &self.ns_commit.commit,
228 &self.ns_mt_proof,
229 )?
230 .is_err()
231 {
232 return Ok(Err(()));
233 }
234 self.ns_proof.verify(param, &self.ns_commit)
235 }
236}
237
238#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
242pub struct NsProof {
243 pub ns_index: usize,
245 #[serde(with = "base64_bytes")]
247 pub ns_payload: Vec<u8>,
248 pub ns_proof: MerkleProof,
250}
251
252impl NsAvidMScheme {
253 pub fn namespace_proof(
256 param: &AvidMParam,
257 payload: &[u8],
258 ns_index: usize,
259 ns_table: impl IntoIterator<Item = Range<usize>>,
260 ) -> VidResult<NsProof> {
261 let ns_table = ns_table.into_iter().collect::<Vec<_>>();
262 let ns_payload_range = ns_table[ns_index].clone();
263 let ns_commits = ns_table
264 .into_iter()
265 .map(|ns_range| {
266 AvidMScheme::commit(param, &payload[ns_range]).map(|commit| commit.commit)
267 })
268 .collect::<Result<Vec<_>, _>>()?;
269 let mt = MerkleTree::from_elems(None, &ns_commits)?;
270 Ok(NsProof {
271 ns_index,
272 ns_payload: payload[ns_payload_range].to_vec(),
273 ns_proof: mt
274 .lookup(ns_index as u64)
275 .expect_ok()
276 .expect("MT lookup shouldn't fail")
277 .1,
278 })
279 }
280
281 pub fn verify_namespace_proof(
283 param: &AvidMParam,
284 commit: &NsAvidMCommit,
285 proof: &NsProof,
286 ) -> VidResult<VerificationResult> {
287 let ns_commit = AvidMScheme::commit(param, &proof.ns_payload)?;
288 Ok(MerkleTree::verify(
289 &commit.commit,
290 proof.ns_index as u64,
291 &ns_commit.commit,
292 &proof.ns_proof,
293 )?)
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use ark_poly::EvaluationDomain;
300 use jf_merkle_tree::MerkleTreeScheme;
301 use rand::{Rng, seq::SliceRandom};
302
303 use crate::{
304 VidError, VidScheme,
305 avidm::{
306 AvidMCommit, AvidMParam, AvidMScheme, AvidMShare, Config, F, MerkleTree, RawAvidMShare,
307 config::AvidMConfig,
308 namespaced::{NsAvidMCommit, NsAvidMScheme, NsAvidMShare},
309 proofs::AvidMBadEncodingProof,
310 radix2_domain,
311 },
312 utils::bytes_to_field,
313 };
314
315 #[test]
316 fn test_proof_of_incorrect_encoding() {
317 let mut rng = jf_utils::test_rng();
318 let param = AvidMScheme::setup(5usize, 10usize).unwrap();
319 let weights = [1u32; 10];
320 let payload_byte_len = bytes_to_field::elem_byte_capacity::<F>() * 4;
321 let domain = radix2_domain::<F>(param.total_weights).unwrap();
322
323 let high_degree_polynomial = vec![F::from(1u64); 10];
324 let mal_payload: Vec<_> = domain
325 .fft(&high_degree_polynomial)
326 .into_iter()
327 .take(param.total_weights)
328 .map(|v| vec![v])
329 .collect();
330
331 let mt = MerkleTree::from_elems(
332 None,
333 mal_payload
334 .iter()
335 .map(|v| Config::raw_share_digest(v).unwrap()),
336 )
337 .unwrap();
338
339 let (commit, mut shares) =
340 AvidMScheme::distribute_shares(¶m, &weights, mt, mal_payload, payload_byte_len)
341 .unwrap();
342
343 shares.shuffle(&mut rng);
344
345 assert!(AvidMScheme::proof_of_incorrect_encoding(¶m, &commit, &shares[..1]).is_err());
347
348 let proof =
350 AvidMScheme::proof_of_incorrect_encoding(¶m, &commit, &shares[..5]).unwrap();
351 assert!(proof.verify(¶m, &commit).unwrap().is_ok());
352
353 let payload = [1u8; 50];
355 let (commit, mut shares) = AvidMScheme::disperse(¶m, &weights, &payload).unwrap();
356 shares.shuffle(&mut rng);
357 assert!(AvidMScheme::proof_of_incorrect_encoding(¶m, &commit, &shares).is_err());
358
359 let witness = AvidMScheme::pad_to_fields(¶m, &payload);
360 let bad_proof = AvidMBadEncodingProof {
361 recovered_poly: witness.clone(),
362 raw_shares: shares
363 .iter()
364 .map(|share| (share.index as usize, share.content.mt_proofs[0].clone()))
365 .collect(),
366 };
367 assert!(bad_proof.verify(¶m, &commit).is_err());
368
369 let mut bad_witness = vec![F::from(0u64); 5];
371 bad_witness[0] = shares[0].content.payload[0][0];
372 let bad_proof2 = AvidMBadEncodingProof {
373 recovered_poly: bad_witness,
374 raw_shares: std::iter::repeat_n(bad_proof.raw_shares[0].clone(), 6).collect(),
375 };
376 assert!(bad_proof2.verify(¶m, &commit).is_err());
377 }
378
379 #[test]
380 fn test_ns_proof() {
381 let param = AvidMScheme::setup(5usize, 10usize).unwrap();
382 let payload = vec![1u8; 100];
383 let ns_table = vec![(0..10), (10..21), (21..33), (33..48), (48..100)];
384 let commit = NsAvidMScheme::commit(¶m, &payload, ns_table.clone()).unwrap();
385
386 for (i, _) in ns_table.iter().enumerate() {
387 let proof =
388 NsAvidMScheme::namespace_proof(¶m, &payload, i, ns_table.clone()).unwrap();
389 assert!(
390 NsAvidMScheme::verify_namespace_proof(¶m, &commit, &proof)
391 .unwrap()
392 .is_ok()
393 );
394 }
395 let mut proof =
396 NsAvidMScheme::namespace_proof(¶m, &payload, 1, ns_table.clone()).unwrap();
397 proof.ns_index = 0;
398 assert!(
399 NsAvidMScheme::verify_namespace_proof(¶m, &commit, &proof)
400 .unwrap()
401 .is_err()
402 );
403 proof.ns_index = 1;
404 proof.ns_payload[0] = 0u8;
405 assert!(
406 NsAvidMScheme::verify_namespace_proof(¶m, &commit, &proof)
407 .unwrap()
408 .is_err()
409 );
410 proof.ns_index = 100;
411 assert!(
412 NsAvidMScheme::verify_namespace_proof(¶m, &commit, &proof)
413 .unwrap()
414 .is_err()
415 );
416 }
417
418 #[test]
419 fn test_ns_proof_of_incorrect_encoding() {
420 let mut rng = jf_utils::test_rng();
421 let param = AvidMScheme::setup(5usize, 10usize).unwrap();
422 let mut payload = [1u8; 100];
423 rng.fill(&mut payload[..]);
424 let distribution = [1u32; 10];
425 let ns_table = [(0..10), (10..21), (21..33), (33..48), (48..100)];
426 let domain = radix2_domain::<F>(param.total_weights).unwrap();
427
428 let mut ns_commits = vec![];
430 let mut disperses = vec![];
431 let mut ns_lens = vec![];
432 for ns_range in ns_table.iter() {
433 ns_lens.push(ns_range.len());
434 if ns_range.start == 10 {
435 let high_degree_polynomial = vec![F::from(1u64); 10];
437 let mal_payload: Vec<_> = domain
438 .fft(&high_degree_polynomial)
439 .into_iter()
440 .take(param.total_weights)
441 .map(|v| vec![v])
442 .collect();
443 let bad_mt = MerkleTree::from_elems(
444 None,
445 mal_payload
446 .iter()
447 .map(|v| Config::raw_share_digest(v).unwrap()),
448 )
449 .unwrap();
450 ns_commits.push(bad_mt.commitment());
451 let shares: Vec<_> = mal_payload
452 .into_iter()
453 .enumerate()
454 .map(|(i, v)| AvidMShare {
455 index: i as u32,
456 payload_byte_len: ns_range.len(),
457 content: RawAvidMShare {
458 range: (i..i + 1),
459 payload: vec![v],
460 mt_proofs: vec![bad_mt.lookup(i as u64).expect_ok().unwrap().1],
461 },
462 })
463 .collect();
464 disperses.push(shares);
465 } else {
466 let (commit, shares) =
467 AvidMScheme::disperse(¶m, &distribution, &payload[ns_range.clone()])
468 .unwrap();
469 ns_commits.push(commit.commit);
470 disperses.push(shares);
471 }
472 }
473 let commit = NsAvidMCommit {
474 commit: MerkleTree::from_elems(None, &ns_commits)
475 .unwrap()
476 .commitment(),
477 };
478 let ns_commits: Vec<_> = ns_commits
479 .into_iter()
480 .map(|comm| AvidMCommit { commit: comm })
481 .collect();
482 let mut shares = vec![NsAvidMShare::default(); disperses[0].len()];
483 shares.iter_mut().for_each(|share| {
484 share.index = disperses[0][0].index;
485 share.ns_commits = ns_commits.clone();
486 share.ns_lens = ns_lens.clone();
487 });
488 disperses.into_iter().for_each(|ns_disperse| {
489 shares
490 .iter_mut()
491 .zip(ns_disperse)
492 .for_each(|(share, ns_share)| share.content.push(ns_share.content))
493 });
494
495 let proof =
497 NsAvidMScheme::proof_of_incorrect_encoding_for_namespace(¶m, 1, &commit, &shares)
498 .unwrap();
499 assert!(proof.verify(¶m, &commit).unwrap().is_ok());
500
501 for ns_index in [0, 2, 3, 4] {
503 assert!(
504 NsAvidMScheme::proof_of_incorrect_encoding_for_namespace(
505 ¶m, ns_index, &commit, &shares
506 )
507 .is_err()
508 );
509 }
510
511 let proof = NsAvidMScheme::proof_of_incorrect_encoding(¶m, &commit, &shares).unwrap();
512 assert_eq!(proof.ns_index, 1);
513 assert!(proof.verify(¶m, &commit).unwrap().is_ok());
514 }
515
516 #[test]
517 fn test_bad_encoding_proof_rejects_boundary_index() {
518 let param = AvidMScheme::setup(3usize, 6usize).unwrap();
521 let payload = [1u8; 50];
522 let weights = [1u32; 6];
523 let (commit, shares) = AvidMScheme::disperse(¶m, &weights, &payload).unwrap();
524
525 let last_share = &shares[5];
528 assert_eq!(last_share.content.range.end, param.total_weights);
529 assert!(
530 AvidMScheme::verify_internal(¶m, &commit, &last_share.content)
531 .is_ok_and(|r| r.is_ok())
532 );
533
534 let mut bad_share = shares[5].clone();
536 bad_share.content.range = param.total_weights..param.total_weights + 1;
537 assert!(
538 AvidMScheme::proof_of_incorrect_encoding(¶m, &commit, &[bad_share]).is_err(),
539 "proof_of_incorrect_encoding must reject share with index >= total_weights"
540 );
541 }
542
543 #[test]
544 fn bad_encoding_proof_rejects_zero_threshold() {
545 let forged = AvidMParam {
546 total_weights: 4,
547 recovery_threshold: 0,
548 };
549 let proof = AvidMBadEncodingProof {
551 recovered_poly: vec![],
552 raw_shares: vec![],
553 };
554 assert!(matches!(
555 proof.verify(&forged, &AvidMCommit::default()),
556 Err(VidError::InvalidParam)
557 ));
558 }
559}