1use rangeset::{Cover, Difference, Subset, ToRangeSet, UnionMut};
4use serde::{Deserialize, Serialize};
5use std::{collections::HashSet, fmt};
6
7use crate::{
8 connection::TranscriptLength,
9 display::FmtRangeSet,
10 hash::{HashAlgId, HashProvider},
11 transcript::{
12 commit::{TranscriptCommitment, TranscriptCommitmentKind},
13 encoding::{EncodingProof, EncodingProofError, EncodingTree},
14 hash::{hash_plaintext, PlaintextHash, PlaintextHashSecret},
15 Direction, PartialTranscript, RangeSet, Transcript, TranscriptSecret,
16 },
17};
18
19const DEFAULT_COMMITMENT_KINDS: &[TranscriptCommitmentKind] = &[
22 TranscriptCommitmentKind::Hash {
23 alg: HashAlgId::SHA256,
24 },
25 TranscriptCommitmentKind::Encoding,
26];
27
28#[derive(Clone, Serialize, Deserialize)]
30pub struct TranscriptProof {
31 transcript: PartialTranscript,
32 encoding_proof: Option<EncodingProof>,
33 hash_secrets: Vec<PlaintextHashSecret>,
34}
35
36opaque_debug::implement!(TranscriptProof);
37
38impl TranscriptProof {
39 pub fn verify_with_provider<'a>(
48 self,
49 provider: &HashProvider,
50 length: &TranscriptLength,
51 commitments: impl IntoIterator<Item = &'a TranscriptCommitment>,
52 ) -> Result<PartialTranscript, TranscriptProofError> {
53 let mut encoding_commitment = None;
54 let mut hash_commitments = HashSet::new();
55 for commitment in commitments {
57 match commitment {
58 TranscriptCommitment::Encoding(commitment) => {
59 if encoding_commitment.replace(commitment).is_some() {
60 return Err(TranscriptProofError::new(
61 ErrorKind::Encoding,
62 "multiple encoding commitments are present.",
63 ));
64 }
65 }
66 TranscriptCommitment::Hash(plaintext_hash) => {
67 hash_commitments.insert(plaintext_hash);
68 }
69 }
70 }
71
72 if self.transcript.sent_unsafe().len() != length.sent as usize
73 || self.transcript.received_unsafe().len() != length.received as usize
74 {
75 return Err(TranscriptProofError::new(
76 ErrorKind::Proof,
77 "transcript has incorrect length",
78 ));
79 }
80
81 let mut total_auth_sent = RangeSet::default();
82 let mut total_auth_recv = RangeSet::default();
83
84 if let Some(proof) = self.encoding_proof {
86 let commitment = encoding_commitment.ok_or_else(|| {
87 TranscriptProofError::new(
88 ErrorKind::Encoding,
89 "contains an encoding proof but missing encoding commitment",
90 )
91 })?;
92
93 let (auth_sent, auth_recv) = proof.verify_with_provider(
94 provider,
95 commitment,
96 self.transcript.sent_unsafe(),
97 self.transcript.received_unsafe(),
98 )?;
99
100 total_auth_sent.union_mut(&auth_sent);
101 total_auth_recv.union_mut(&auth_recv);
102 }
103
104 let mut buffer = Vec::new();
105 for PlaintextHashSecret {
106 direction,
107 idx,
108 alg,
109 blinder,
110 } in self.hash_secrets
111 {
112 let hasher = provider.get(&alg).map_err(|_| {
113 TranscriptProofError::new(
114 ErrorKind::Hash,
115 format!("hash opening has unknown algorithm: {alg}"),
116 )
117 })?;
118
119 let (plaintext, auth) = match direction {
120 Direction::Sent => (self.transcript.sent_unsafe(), &mut total_auth_sent),
121 Direction::Received => (self.transcript.received_unsafe(), &mut total_auth_recv),
122 };
123
124 if idx.end().unwrap_or(0) > plaintext.len() {
125 return Err(TranscriptProofError::new(
126 ErrorKind::Hash,
127 "hash opening index is out of bounds",
128 ));
129 }
130
131 buffer.clear();
132 for range in idx.iter_ranges() {
133 buffer.extend_from_slice(&plaintext[range]);
134 }
135
136 let expected = PlaintextHash {
137 direction,
138 idx,
139 hash: hash_plaintext(hasher, &buffer, &blinder),
140 };
141
142 if !hash_commitments.contains(&expected) {
143 return Err(TranscriptProofError::new(
144 ErrorKind::Hash,
145 "hash opening does not match any commitment",
146 ));
147 }
148
149 auth.union_mut(&expected.idx);
150 }
151
152 if &total_auth_sent != self.transcript.sent_authed()
154 || &total_auth_recv != self.transcript.received_authed()
155 {
156 return Err(TranscriptProofError::new(
157 ErrorKind::Proof,
158 "transcript proof contains unauthenticated data",
159 ));
160 }
161
162 Ok(self.transcript)
163 }
164}
165
166#[derive(Debug, thiserror::Error)]
168pub struct TranscriptProofError {
169 kind: ErrorKind,
170 source: Option<Box<dyn std::error::Error + Send + Sync>>,
171}
172
173impl TranscriptProofError {
174 fn new<E>(kind: ErrorKind, source: E) -> Self
175 where
176 E: Into<Box<dyn std::error::Error + Send + Sync>>,
177 {
178 Self {
179 kind,
180 source: Some(source.into()),
181 }
182 }
183}
184
185#[derive(Debug)]
186enum ErrorKind {
187 Encoding,
188 Hash,
189 Proof,
190}
191
192impl fmt::Display for TranscriptProofError {
193 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194 f.write_str("transcript proof error: ")?;
195
196 match self.kind {
197 ErrorKind::Encoding => f.write_str("encoding error")?,
198 ErrorKind::Hash => f.write_str("hash error")?,
199 ErrorKind::Proof => f.write_str("proof error")?,
200 }
201
202 if let Some(source) = &self.source {
203 write!(f, " caused by: {source}")?;
204 }
205
206 Ok(())
207 }
208}
209
210impl From<EncodingProofError> for TranscriptProofError {
211 fn from(e: EncodingProofError) -> Self {
212 TranscriptProofError::new(ErrorKind::Encoding, e)
213 }
214}
215
216#[derive(Clone, Debug, PartialEq)]
218struct QueryIdx {
219 sent: RangeSet<usize>,
220 recv: RangeSet<usize>,
221}
222
223impl QueryIdx {
224 fn new() -> Self {
225 Self {
226 sent: RangeSet::default(),
227 recv: RangeSet::default(),
228 }
229 }
230
231 fn is_empty(&self) -> bool {
232 self.sent.is_empty() && self.recv.is_empty()
233 }
234
235 fn union(&mut self, direction: &Direction, other: &RangeSet<usize>) {
236 match direction {
237 Direction::Sent => self.sent.union_mut(other),
238 Direction::Received => self.recv.union_mut(other),
239 }
240 }
241}
242
243impl std::fmt::Display for QueryIdx {
244 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
245 write!(
246 f,
247 "sent: {}, received: {}",
248 FmtRangeSet(&self.sent),
249 FmtRangeSet(&self.recv)
250 )
251 }
252}
253
254#[derive(Debug)]
256pub struct TranscriptProofBuilder<'a> {
257 commitment_kinds: Vec<TranscriptCommitmentKind>,
259 transcript: &'a Transcript,
260 encoding_tree: Option<&'a EncodingTree>,
261 hash_secrets: Vec<&'a PlaintextHashSecret>,
262 committed_sent: RangeSet<usize>,
263 committed_recv: RangeSet<usize>,
264 query_idx: QueryIdx,
265}
266
267impl<'a> TranscriptProofBuilder<'a> {
268 pub fn new(
270 transcript: &'a Transcript,
271 secrets: impl IntoIterator<Item = &'a TranscriptSecret>,
272 ) -> Self {
273 let mut committed_sent = RangeSet::default();
274 let mut committed_recv = RangeSet::default();
275
276 let mut encoding_tree = None;
277 let mut hash_secrets = Vec::new();
278 for secret in secrets {
279 match secret {
280 TranscriptSecret::Encoding(tree) => {
281 committed_sent.union_mut(tree.idx(Direction::Sent));
282 committed_recv.union_mut(tree.idx(Direction::Received));
283 encoding_tree = Some(tree);
284 }
285 TranscriptSecret::Hash(hash) => {
286 match hash.direction {
287 Direction::Sent => committed_sent.union_mut(&hash.idx),
288 Direction::Received => committed_recv.union_mut(&hash.idx),
289 }
290 hash_secrets.push(hash);
291 }
292 }
293 }
294
295 Self {
296 commitment_kinds: DEFAULT_COMMITMENT_KINDS.to_vec(),
297 transcript,
298 encoding_tree,
299 hash_secrets,
300 committed_sent,
301 committed_recv,
302 query_idx: QueryIdx::new(),
303 }
304 }
305
306 pub fn commitment_kinds(&mut self, kinds: &[TranscriptCommitmentKind]) -> &mut Self {
309 if !kinds.is_empty() {
310 let mut seen = HashSet::new();
312 self.commitment_kinds = kinds
313 .iter()
314 .filter(|&kind| seen.insert(kind))
315 .cloned()
316 .collect();
317 }
318 self
319 }
320
321 pub fn reveal(
328 &mut self,
329 ranges: &dyn ToRangeSet<usize>,
330 direction: Direction,
331 ) -> Result<&mut Self, TranscriptProofBuilderError> {
332 let idx = ranges.to_range_set();
333
334 if idx.end().unwrap_or(0) > self.transcript.len_of_direction(direction) {
335 return Err(TranscriptProofBuilderError::new(
336 BuilderErrorKind::Index,
337 format!(
338 "range is out of bounds of the transcript ({}): {} > {}",
339 direction,
340 idx.end().unwrap_or(0),
341 self.transcript.len_of_direction(direction)
342 ),
343 ));
344 }
345
346 let committed = match direction {
347 Direction::Sent => &self.committed_sent,
348 Direction::Received => &self.committed_recv,
349 };
350
351 if idx.is_subset(committed) {
352 self.query_idx.union(&direction, &idx);
353 } else {
354 let missing = idx.difference(committed);
355 return Err(TranscriptProofBuilderError::new(
356 BuilderErrorKind::MissingCommitment,
357 format!(
358 "commitment is missing for ranges in {direction} transcript: {}",
359 FmtRangeSet(&missing)
360 ),
361 ));
362 }
363 Ok(self)
364 }
365
366 pub fn reveal_sent(
372 &mut self,
373 ranges: &dyn ToRangeSet<usize>,
374 ) -> Result<&mut Self, TranscriptProofBuilderError> {
375 self.reveal(ranges, Direction::Sent)
376 }
377
378 pub fn reveal_recv(
384 &mut self,
385 ranges: &dyn ToRangeSet<usize>,
386 ) -> Result<&mut Self, TranscriptProofBuilderError> {
387 self.reveal(ranges, Direction::Received)
388 }
389
390 pub fn build(self) -> Result<TranscriptProof, TranscriptProofBuilderError> {
392 let mut transcript_proof = TranscriptProof {
393 transcript: self
394 .transcript
395 .to_partial(self.query_idx.sent.clone(), self.query_idx.recv.clone()),
396 encoding_proof: None,
397 hash_secrets: Vec::new(),
398 };
399 let mut uncovered_query_idx = self.query_idx.clone();
400 let mut commitment_kinds_iter = self.commitment_kinds.iter();
401
402 while !uncovered_query_idx.is_empty() {
404 if let Some(kind) = commitment_kinds_iter.next() {
407 match kind {
408 TranscriptCommitmentKind::Encoding => {
409 let Some(encoding_tree) = self.encoding_tree else {
410 continue;
413 };
414
415 let (sent_dir_idxs, sent_uncovered) = uncovered_query_idx.sent.cover_by(
416 encoding_tree
417 .transcript_indices()
418 .filter(|(dir, _)| *dir == Direction::Sent),
419 |(_, idx)| idx,
420 );
421 uncovered_query_idx.sent = sent_uncovered;
424
425 let (recv_dir_idxs, recv_uncovered) = uncovered_query_idx.recv.cover_by(
426 encoding_tree
427 .transcript_indices()
428 .filter(|(dir, _)| *dir == Direction::Received),
429 |(_, idx)| idx,
430 );
431 uncovered_query_idx.recv = recv_uncovered;
432
433 let dir_idxs = sent_dir_idxs
434 .into_iter()
435 .chain(recv_dir_idxs)
436 .collect::<Vec<_>>();
437
438 if !dir_idxs.is_empty() {
441 transcript_proof.encoding_proof = Some(
442 encoding_tree
443 .proof(dir_idxs.into_iter())
444 .expect("subsequences were checked to be in tree"),
445 );
446 }
447 }
448 TranscriptCommitmentKind::Hash { alg } => {
449 let (sent_hashes, sent_uncovered) = uncovered_query_idx.sent.cover_by(
450 self.hash_secrets.iter().filter(|hash| {
451 hash.direction == Direction::Sent && &hash.alg == alg
452 }),
453 |hash| &hash.idx,
454 );
455 uncovered_query_idx.sent = sent_uncovered;
458
459 let (recv_hashes, recv_uncovered) = uncovered_query_idx.recv.cover_by(
460 self.hash_secrets.iter().filter(|hash| {
461 hash.direction == Direction::Received && &hash.alg == alg
462 }),
463 |hash| &hash.idx,
464 );
465 uncovered_query_idx.recv = recv_uncovered;
466
467 transcript_proof.hash_secrets.extend(
468 sent_hashes
469 .into_iter()
470 .map(|s| PlaintextHashSecret::clone(s)),
471 );
472 transcript_proof.hash_secrets.extend(
473 recv_hashes
474 .into_iter()
475 .map(|s| PlaintextHashSecret::clone(s)),
476 );
477 }
478 #[allow(unreachable_patterns)]
479 kind => {
480 return Err(TranscriptProofBuilderError::new(
481 BuilderErrorKind::NotSupported,
482 format!("opening {kind} transcript commitments is not yet supported"),
483 ));
484 }
485 }
486 } else {
487 break;
489 }
490 }
491
492 if !uncovered_query_idx.is_empty() {
495 return Err(TranscriptProofBuilderError::cover(
496 uncovered_query_idx,
497 &self.commitment_kinds,
498 ));
499 }
500
501 Ok(transcript_proof)
502 }
503}
504
505#[derive(Debug, thiserror::Error)]
507pub struct TranscriptProofBuilderError {
508 kind: BuilderErrorKind,
509 source: Option<Box<dyn std::error::Error + Send + Sync>>,
510}
511
512impl TranscriptProofBuilderError {
513 fn new<E>(kind: BuilderErrorKind, source: E) -> Self
514 where
515 E: Into<Box<dyn std::error::Error + Send + Sync>>,
516 {
517 Self {
518 kind,
519 source: Some(source.into()),
520 }
521 }
522
523 fn cover(uncovered: QueryIdx, kinds: &[TranscriptCommitmentKind]) -> Self {
524 Self {
525 kind: BuilderErrorKind::Cover {
526 uncovered,
527 kinds: kinds.to_vec(),
528 },
529 source: None,
530 }
531 }
532}
533
534#[derive(Debug, PartialEq)]
535enum BuilderErrorKind {
536 Index,
537 MissingCommitment,
538 Cover {
539 uncovered: QueryIdx,
540 kinds: Vec<TranscriptCommitmentKind>,
541 },
542 NotSupported,
543}
544
545impl fmt::Display for TranscriptProofBuilderError {
546 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
547 f.write_str("transcript proof builder error: ")?;
548
549 match &self.kind {
550 BuilderErrorKind::Index => f.write_str("index error")?,
551 BuilderErrorKind::MissingCommitment => f.write_str("commitment error")?,
552 BuilderErrorKind::Cover { uncovered, kinds } => f.write_str(&format!(
553 "unable to cover the following ranges in transcript using available {kinds:?} commitments: {uncovered}"
554 ))?,
555 BuilderErrorKind::NotSupported => f.write_str("not supported")?,
556 }
557
558 if let Some(source) = &self.source {
559 write!(f, " caused by: {source}")?;
560 }
561
562 Ok(())
563 }
564}
565
566#[allow(clippy::single_range_in_vec_init)]
567#[cfg(test)]
568mod tests {
569 use rand::{Rng, SeedableRng};
570 use rangeset::RangeSet;
571 use rstest::rstest;
572 use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON};
573
574 use crate::{
575 fixtures::encoding_provider,
576 hash::{Blake3, Blinder, HashAlgId},
577 transcript::TranscriptCommitConfigBuilder,
578 };
579
580 use super::*;
581
582 #[rstest]
583 fn test_verify_missing_encoding_commitment_root() {
584 let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
585 let idxs = vec![(Direction::Received, RangeSet::from(0..transcript.len().1))];
586 let encoding_tree = EncodingTree::new(
587 &Blake3::default(),
588 &idxs,
589 &encoding_provider(transcript.sent(), transcript.received()),
590 )
591 .unwrap();
592
593 let secrets = vec![TranscriptSecret::Encoding(encoding_tree)];
594 let mut builder = TranscriptProofBuilder::new(&transcript, &secrets);
595
596 builder.reveal_recv(&(0..transcript.len().1)).unwrap();
597
598 let transcript_proof = builder.build().unwrap();
599
600 let provider = HashProvider::default();
601 let err = transcript_proof
602 .verify_with_provider(&provider, &transcript.length(), &[])
603 .err()
604 .unwrap();
605
606 assert!(matches!(err.kind, ErrorKind::Encoding));
607 }
608
609 #[rstest]
610 fn test_reveal_range_out_of_bounds() {
611 let transcript = Transcript::new(
612 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
613 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
614 );
615 let mut builder = TranscriptProofBuilder::new(&transcript, &[]);
616
617 let err = builder.reveal(&(10..15), Direction::Sent).unwrap_err();
618 assert!(matches!(err.kind, BuilderErrorKind::Index));
619
620 let err = builder
621 .reveal(&(10..15), Direction::Received)
622 .err()
623 .unwrap();
624 assert!(matches!(err.kind, BuilderErrorKind::Index));
625 }
626
627 #[rstest]
628 fn test_reveal_missing_encoding_tree() {
629 let transcript = Transcript::new(
630 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
631 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
632 );
633 let mut builder = TranscriptProofBuilder::new(&transcript, &[]);
634
635 let err = builder.reveal_recv(&(9..11)).unwrap_err();
636 assert!(matches!(err.kind, BuilderErrorKind::MissingCommitment));
637 }
638
639 #[rstest]
640 fn test_reveal_with_hash_commitment() {
641 let mut rng = rand::rngs::StdRng::seed_from_u64(0);
642 let provider = HashProvider::default();
643 let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
644
645 let direction = Direction::Sent;
646 let idx = RangeSet::from(0..10);
647 let blinder: Blinder = rng.random();
648 let alg = HashAlgId::SHA256;
649 let hasher = provider.get(&alg).unwrap();
650
651 let commitment = PlaintextHash {
652 direction,
653 idx: idx.clone(),
654 hash: hash_plaintext(hasher, &transcript.sent()[0..10], &blinder),
655 };
656
657 let secret = PlaintextHashSecret {
658 direction,
659 idx: idx.clone(),
660 alg,
661 blinder,
662 };
663
664 let secrets = vec![TranscriptSecret::Hash(secret)];
665 let mut builder = TranscriptProofBuilder::new(&transcript, &secrets);
666
667 builder.reveal_sent(&(0..10)).unwrap();
668
669 let transcript_proof = builder.build().unwrap();
670
671 let partial_transcript = transcript_proof
672 .verify_with_provider(
673 &provider,
674 &transcript.length(),
675 &[TranscriptCommitment::Hash(commitment)],
676 )
677 .unwrap();
678
679 assert_eq!(
680 partial_transcript.sent_unsafe()[0..10],
681 transcript.sent()[0..10]
682 );
683 }
684
685 #[rstest]
686 fn test_reveal_with_inconsistent_hash_commitment() {
687 let mut rng = rand::rngs::StdRng::seed_from_u64(0);
688 let provider = HashProvider::default();
689 let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
690
691 let direction = Direction::Sent;
692 let idx = RangeSet::from(0..10);
693 let blinder: Blinder = rng.random();
694 let alg = HashAlgId::SHA256;
695 let hasher = provider.get(&alg).unwrap();
696
697 let commitment = PlaintextHash {
698 direction,
699 idx: idx.clone(),
700 hash: hash_plaintext(hasher, &transcript.sent()[0..10], &blinder),
701 };
702
703 let secret = PlaintextHashSecret {
704 direction,
705 idx: idx.clone(),
706 alg,
707 blinder: rng.random(),
709 };
710
711 let secrets = vec![TranscriptSecret::Hash(secret)];
712 let mut builder = TranscriptProofBuilder::new(&transcript, &secrets);
713
714 builder.reveal_sent(&(0..10)).unwrap();
715
716 let transcript_proof = builder.build().unwrap();
717
718 let err = transcript_proof
719 .verify_with_provider(
720 &provider,
721 &transcript.length(),
722 &[TranscriptCommitment::Hash(commitment)],
723 )
724 .unwrap_err();
725
726 assert!(matches!(err.kind, ErrorKind::Hash));
727 }
728
729 #[rstest]
730 fn test_set_commitment_kinds_with_duplicates() {
731 let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
732 let mut builder = TranscriptProofBuilder::new(&transcript, &[]);
733 builder.commitment_kinds(&[
734 TranscriptCommitmentKind::Hash {
735 alg: HashAlgId::SHA256,
736 },
737 TranscriptCommitmentKind::Encoding,
738 TranscriptCommitmentKind::Hash {
739 alg: HashAlgId::SHA256,
740 },
741 TranscriptCommitmentKind::Hash {
742 alg: HashAlgId::SHA256,
743 },
744 TranscriptCommitmentKind::Encoding,
745 ]);
746
747 assert_eq!(
748 builder.commitment_kinds,
749 vec![
750 TranscriptCommitmentKind::Hash {
751 alg: HashAlgId::SHA256
752 },
753 TranscriptCommitmentKind::Encoding
754 ]
755 );
756 }
757
758 #[rstest]
759 #[case::reveal_all_rangesets_with_exact_set(
760 vec![RangeSet::from([0..10]), RangeSet::from([12..30]), RangeSet::from([0..5, 15..30]), RangeSet::from([70..75, 85..100])],
761 RangeSet::from([0..10, 12..30]),
762 true,
763 )]
764 #[case::reveal_all_rangesets_with_superset_ranges(
765 vec![RangeSet::from([0..1]), RangeSet::from([1..2, 8..9]), RangeSet::from([2..4, 6..8]), RangeSet::from([2..3, 6..7]), RangeSet::from([9..12])],
766 RangeSet::from([0..4, 6..9]),
767 true,
768 )]
769 #[case::reveal_all_rangesets_with_superset_range(
770 vec![RangeSet::from([0..1, 2..4]), RangeSet::from([1..3]), RangeSet::from([1..9]), RangeSet::from([2..3])],
771 RangeSet::from([0..4]),
772 true,
773 )]
774 #[case::failed_to_reveal_with_superset_range_missing_within(
775 vec![RangeSet::from([0..20, 45..56]), RangeSet::from([80..120]), RangeSet::from([50..53])],
776 RangeSet::from([0..120]),
777 false,
778 )]
779 #[case::failed_to_reveal_with_superset_range_missing_outside(
780 vec![RangeSet::from([2..20, 45..116]), RangeSet::from([20..45]), RangeSet::from([50..53])],
781 RangeSet::from([0..120]),
782 false,
783 )]
784 #[case::failed_to_reveal_with_superset_ranges_missing_outside(
785 vec![RangeSet::from([1..10]), RangeSet::from([1..20]), RangeSet::from([15..20, 75..110])],
786 RangeSet::from([0..41, 74..100]),
787 false,
788 )]
789 #[case::failed_to_reveal_as_no_subset_range(
790 vec![RangeSet::from([2..4]), RangeSet::from([1..2]), RangeSet::from([1..9]), RangeSet::from([2..3])],
791 RangeSet::from([0..1]),
792 false,
793 )]
794 #[allow(clippy::single_range_in_vec_init)]
795 fn test_reveal_mutliple_rangesets_with_one_rangeset(
796 #[case] commit_recv_rangesets: Vec<RangeSet<usize>>,
797 #[case] reveal_recv_rangeset: RangeSet<usize>,
798 #[case] success: bool,
799 ) {
800 let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
801
802 let mut transcript_commitment_builder = TranscriptCommitConfigBuilder::new(&transcript);
804 for rangeset in commit_recv_rangesets.iter() {
805 transcript_commitment_builder.commit_recv(rangeset).unwrap();
806 }
807
808 let transcripts_commitment_config = transcript_commitment_builder.build().unwrap();
809
810 let encoding_tree = EncodingTree::new(
811 &Blake3::default(),
812 transcripts_commitment_config.iter_encoding(),
813 &encoding_provider(GET_WITH_HEADER, OK_JSON),
814 )
815 .unwrap();
816
817 let secrets = vec![TranscriptSecret::Encoding(encoding_tree)];
818 let mut builder = TranscriptProofBuilder::new(&transcript, &secrets);
819
820 if success {
821 assert!(builder.reveal_recv(&reveal_recv_rangeset).is_ok());
822 } else {
823 let err = builder.reveal_recv(&reveal_recv_rangeset).unwrap_err();
824 assert!(matches!(err.kind, BuilderErrorKind::MissingCommitment));
825 }
826 }
827
828 #[rstest]
829 #[case::cover(
830 vec![RangeSet::from([1..5, 6..10])],
831 vec![RangeSet::from([2..4, 8..10])],
832 RangeSet::from([1..5, 6..10]),
833 RangeSet::from([2..4, 8..10]),
834 RangeSet::default(),
835 RangeSet::default(),
836 )]
837 #[case::failed_to_cover_sent(
838 vec![RangeSet::from([1..5, 6..10])],
839 vec![RangeSet::from([2..4, 8..10])],
840 RangeSet::from([1..5]),
841 RangeSet::from([2..4, 8..10]),
842 RangeSet::from([1..5]),
843 RangeSet::default(),
844 )]
845 #[case::failed_to_cover_recv(
846 vec![RangeSet::from([1..5, 6..10])],
847 vec![RangeSet::from([2..4, 8..10])],
848 RangeSet::from([1..5, 6..10]),
849 RangeSet::from([2..4]),
850 RangeSet::default(),
851 RangeSet::from([2..4]),
852 )]
853 #[case::failed_to_cover_both(
854 vec![RangeSet::from([1..5, 6..10])],
855 vec![RangeSet::from([2..4, 8..10])],
856 RangeSet::from([1..5]),
857 RangeSet::from([2..4]),
858 RangeSet::from([1..5]),
859 RangeSet::from([2..4]),
860 )]
861 #[allow(clippy::single_range_in_vec_init)]
862 fn test_transcript_proof_builder(
863 #[case] commit_sent_rangesets: Vec<RangeSet<usize>>,
864 #[case] commit_recv_rangesets: Vec<RangeSet<usize>>,
865 #[case] reveal_sent_rangeset: RangeSet<usize>,
866 #[case] reveal_recv_rangeset: RangeSet<usize>,
867 #[case] uncovered_sent_rangeset: RangeSet<usize>,
868 #[case] uncovered_recv_rangeset: RangeSet<usize>,
869 ) {
870 let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
871
872 let mut transcript_commitment_builder = TranscriptCommitConfigBuilder::new(&transcript);
874 for rangeset in commit_sent_rangesets.iter() {
875 transcript_commitment_builder.commit_sent(rangeset).unwrap();
876 }
877 for rangeset in commit_recv_rangesets.iter() {
878 transcript_commitment_builder.commit_recv(rangeset).unwrap();
879 }
880
881 let transcripts_commitment_config = transcript_commitment_builder.build().unwrap();
882
883 let encoding_tree = EncodingTree::new(
884 &Blake3::default(),
885 transcripts_commitment_config.iter_encoding(),
886 &encoding_provider(GET_WITH_HEADER, OK_JSON),
887 )
888 .unwrap();
889
890 let secrets = vec![TranscriptSecret::Encoding(encoding_tree)];
891 let mut builder = TranscriptProofBuilder::new(&transcript, &secrets);
892 builder.reveal_sent(&reveal_sent_rangeset).unwrap();
893 builder.reveal_recv(&reveal_recv_rangeset).unwrap();
894
895 if uncovered_sent_rangeset.is_empty() && uncovered_recv_rangeset.is_empty() {
896 assert!(builder.build().is_ok());
897 } else {
898 let TranscriptProofBuilderError { kind, .. } = builder.build().unwrap_err();
899 match kind {
900 BuilderErrorKind::Cover { uncovered, .. } => {
901 if !uncovered_sent_rangeset.is_empty() {
902 assert_eq!(uncovered.sent, uncovered_sent_rangeset);
903 }
904 if !uncovered_recv_rangeset.is_empty() {
905 assert_eq!(uncovered.recv, uncovered_recv_rangeset);
906 }
907 }
908 _ => panic!("unexpected error kind: {kind:?}"),
909 }
910 }
911 }
912}