1use rangeset::{Cover, ToRangeSet};
4use serde::{Deserialize, Serialize};
5use std::{collections::HashSet, fmt};
6
7use crate::{
8 attestation::Body,
9 index::Index,
10 transcript::{
11 commit::{TranscriptCommitmentKind, MAX_TOTAL_COMMITTED_DATA},
12 encoding::{EncodingProof, EncodingProofError, EncodingTree},
13 hash::{PlaintextHashProof, PlaintextHashProofError, PlaintextHashSecret},
14 Direction, Idx, PartialTranscript, Transcript,
15 },
16 CryptoProvider,
17};
18
19const DEFAULT_COMMITMENT_KINDS: &[TranscriptCommitmentKind] = &[TranscriptCommitmentKind::Encoding];
22
23#[derive(Clone, Serialize, Deserialize)]
25pub struct TranscriptProof {
26 encoding_proof: Option<EncodingProof>,
27 hash_proofs: Vec<PlaintextHashProof>,
28}
29
30opaque_debug::implement!(TranscriptProof);
31
32impl TranscriptProof {
33 pub fn verify_with_provider(
42 self,
43 provider: &CryptoProvider,
44 attestation_body: &Body,
45 ) -> Result<PartialTranscript, TranscriptProofError> {
46 let info = attestation_body.connection_info();
47
48 let mut transcript = PartialTranscript::new(
49 info.transcript_length.sent as usize,
50 info.transcript_length.received as usize,
51 );
52
53 if let Some(proof) = self.encoding_proof {
55 let commitment = attestation_body.encoding_commitment().ok_or_else(|| {
56 TranscriptProofError::new(
57 ErrorKind::Encoding,
58 "contains an encoding proof but attestation is missing encoding commitment",
59 )
60 })?;
61 let seq = proof.verify_with_provider(provider, &info.transcript_length, commitment)?;
62 transcript.union_transcript(&seq);
63 }
64
65 let mut total_opened = 0u128;
67
68 for proof in self.hash_proofs {
69 let commitment = attestation_body
70 .plaintext_hashes()
71 .get_by_field_id(proof.commitment_id())
72 .map(|field| &field.data)
73 .ok_or_else(|| {
74 TranscriptProofError::new(
75 ErrorKind::Hash,
76 format!("contains a hash opening but attestation is missing corresponding commitment (id: {})", proof.commitment_id()),
77 )
78 })?;
79
80 total_opened += commitment.idx.len() as u128;
82 if total_opened > MAX_TOTAL_COMMITTED_DATA as u128 {
83 return Err(TranscriptProofError::new(
84 ErrorKind::Hash,
85 "exceeded maximum allowed data",
86 ))?;
87 }
88
89 let (direction, seq) = proof.verify(&provider.hash, commitment)?;
90 transcript.union_subsequence(direction, &seq);
91 }
92
93 Ok(transcript)
94 }
95}
96
97#[derive(Debug, thiserror::Error)]
99pub struct TranscriptProofError {
100 kind: ErrorKind,
101 source: Option<Box<dyn std::error::Error + Send + Sync>>,
102}
103
104impl TranscriptProofError {
105 fn new<E>(kind: ErrorKind, source: E) -> Self
106 where
107 E: Into<Box<dyn std::error::Error + Send + Sync>>,
108 {
109 Self {
110 kind,
111 source: Some(source.into()),
112 }
113 }
114}
115
116#[derive(Debug)]
117enum ErrorKind {
118 Encoding,
119 Hash,
120}
121
122impl fmt::Display for TranscriptProofError {
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 f.write_str("transcript proof error: ")?;
125
126 match self.kind {
127 ErrorKind::Encoding => f.write_str("encoding error")?,
128 ErrorKind::Hash => f.write_str("hash error")?,
129 }
130
131 if let Some(source) = &self.source {
132 write!(f, " caused by: {}", source)?;
133 }
134
135 Ok(())
136 }
137}
138
139impl From<EncodingProofError> for TranscriptProofError {
140 fn from(e: EncodingProofError) -> Self {
141 TranscriptProofError::new(ErrorKind::Encoding, e)
142 }
143}
144
145impl From<PlaintextHashProofError> for TranscriptProofError {
146 fn from(e: PlaintextHashProofError) -> Self {
147 TranscriptProofError::new(ErrorKind::Hash, e)
148 }
149}
150
151#[derive(Debug)]
153struct CommittedIdx {
154 sent: Idx,
155 recv: Idx,
156}
157
158impl CommittedIdx {
159 fn new(
160 encoding_tree: Option<&EncodingTree>,
161 plaintext_hashes: &Index<PlaintextHashSecret>,
162 ) -> Self {
163 let mut sent = plaintext_hashes.idx(Direction::Sent).clone();
164 let mut recv = plaintext_hashes.idx(Direction::Received).clone();
165
166 if let Some(tree) = encoding_tree {
167 sent.union_mut(tree.idx(Direction::Sent));
168 recv.union_mut(tree.idx(Direction::Received));
169 }
170
171 Self { sent, recv }
172 }
173
174 fn idx(&self, direction: &Direction) -> &Idx {
175 match direction {
176 Direction::Sent => &self.sent,
177 Direction::Received => &self.recv,
178 }
179 }
180}
181
182#[derive(Clone, Debug, PartialEq)]
184struct QueryIdx {
185 sent: Idx,
186 recv: Idx,
187}
188
189impl QueryIdx {
190 fn new() -> Self {
191 Self {
192 sent: Idx::empty(),
193 recv: Idx::empty(),
194 }
195 }
196
197 fn is_empty(&self) -> bool {
198 self.sent.is_empty() && self.recv.is_empty()
199 }
200
201 fn union(&mut self, direction: &Direction, other: &Idx) {
202 match direction {
203 Direction::Sent => self.sent.union_mut(other),
204 Direction::Received => self.recv.union_mut(other),
205 }
206 }
207}
208
209impl std::fmt::Display for QueryIdx {
210 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211 write!(f, "sent: {}, received: {}", self.sent, self.recv)
212 }
213}
214
215#[derive(Debug)]
217pub struct TranscriptProofBuilder<'a> {
218 commitment_kinds: Vec<TranscriptCommitmentKind>,
220 transcript: &'a Transcript,
221 encoding_tree: Option<&'a EncodingTree>,
222 #[allow(dead_code)]
223 plaintext_hashes: &'a Index<PlaintextHashSecret>,
224 committed_idx: CommittedIdx,
225 query_idx: QueryIdx,
226}
227
228impl<'a> TranscriptProofBuilder<'a> {
229 pub(crate) fn new(
231 transcript: &'a Transcript,
232 encoding_tree: Option<&'a EncodingTree>,
233 plaintext_hashes: &'a Index<PlaintextHashSecret>,
234 ) -> Self {
235 Self {
236 commitment_kinds: DEFAULT_COMMITMENT_KINDS.to_vec(),
237 transcript,
238 encoding_tree,
239 plaintext_hashes,
240 committed_idx: CommittedIdx::new(encoding_tree, plaintext_hashes),
241 query_idx: QueryIdx::new(),
242 }
243 }
244
245 pub fn commitment_kinds(&mut self, kinds: &[TranscriptCommitmentKind]) -> &mut Self {
248 if !kinds.is_empty() {
249 let mut seen = HashSet::new();
251 self.commitment_kinds = kinds
252 .iter()
253 .filter(|&kind| seen.insert(kind))
254 .cloned()
255 .collect();
256 }
257 self
258 }
259
260 pub fn reveal(
267 &mut self,
268 ranges: &dyn ToRangeSet<usize>,
269 direction: Direction,
270 ) -> Result<&mut Self, TranscriptProofBuilderError> {
271 let idx = Idx::new(ranges.to_range_set());
272
273 if idx.end() > self.transcript.len_of_direction(direction) {
274 return Err(TranscriptProofBuilderError::new(
275 BuilderErrorKind::Index,
276 format!(
277 "range is out of bounds of the transcript ({}): {} > {}",
278 direction,
279 idx.end(),
280 self.transcript.len_of_direction(direction)
281 ),
282 ));
283 }
284
285 if idx.is_subset(self.committed_idx.idx(&direction)) {
286 self.query_idx.union(&direction, &idx);
287 } else {
288 let missing = idx.difference(self.committed_idx.idx(&direction));
289 return Err(TranscriptProofBuilderError::new(
290 BuilderErrorKind::MissingCommitment,
291 format!("commitment is missing for ranges in {direction} transcript: {missing}"),
292 ));
293 }
294 Ok(self)
295 }
296
297 pub fn reveal_sent(
303 &mut self,
304 ranges: &dyn ToRangeSet<usize>,
305 ) -> Result<&mut Self, TranscriptProofBuilderError> {
306 self.reveal(ranges, Direction::Sent)
307 }
308
309 pub fn reveal_recv(
315 &mut self,
316 ranges: &dyn ToRangeSet<usize>,
317 ) -> Result<&mut Self, TranscriptProofBuilderError> {
318 self.reveal(ranges, Direction::Received)
319 }
320
321 pub fn build(self) -> Result<TranscriptProof, TranscriptProofBuilderError> {
323 let mut transcript_proof = TranscriptProof {
324 encoding_proof: None,
325 hash_proofs: Vec::new(),
326 };
327 let mut uncovered_query_idx = self.query_idx.clone();
328 let mut commitment_kinds_iter = self.commitment_kinds.iter();
329
330 while !uncovered_query_idx.is_empty() {
332 if let Some(kind) = commitment_kinds_iter.next() {
335 match kind {
336 TranscriptCommitmentKind::Encoding => {
337 let Some(encoding_tree) = self.encoding_tree else {
338 continue;
341 };
342
343 let (sent_dir_idxs, sent_uncovered) =
344 uncovered_query_idx.sent.as_range_set().cover_by(
345 encoding_tree
346 .transcript_indices()
347 .filter(|(dir, _)| *dir == Direction::Sent),
348 |(_, idx)| &idx.0,
349 );
350 uncovered_query_idx.sent = Idx(sent_uncovered);
353
354 let (recv_dir_idxs, recv_uncovered) =
355 uncovered_query_idx.recv.as_range_set().cover_by(
356 encoding_tree
357 .transcript_indices()
358 .filter(|(dir, _)| *dir == Direction::Received),
359 |(_, idx)| &idx.0,
360 );
361 uncovered_query_idx.recv = Idx(recv_uncovered);
362
363 let dir_idxs = sent_dir_idxs
364 .into_iter()
365 .chain(recv_dir_idxs)
366 .collect::<Vec<_>>();
367
368 if !dir_idxs.is_empty() {
371 transcript_proof.encoding_proof = Some(
372 encoding_tree
373 .proof(self.transcript, dir_idxs.into_iter())
374 .expect("subsequences were checked to be in tree"),
375 );
376 }
377 }
378 kind => {
379 return Err(TranscriptProofBuilderError::new(
380 BuilderErrorKind::NotSupported,
381 format!("opening {kind} transcript commitments is not yet supported"),
382 ));
383 }
384 }
385 } else {
386 break;
388 }
389 }
390
391 if !uncovered_query_idx.is_empty() {
394 return Err(TranscriptProofBuilderError::cover(
395 uncovered_query_idx,
396 &self.commitment_kinds,
397 ));
398 }
399
400 Ok(transcript_proof)
401 }
402}
403
404#[derive(Debug, thiserror::Error)]
406pub struct TranscriptProofBuilderError {
407 kind: BuilderErrorKind,
408 source: Option<Box<dyn std::error::Error + Send + Sync>>,
409}
410
411impl TranscriptProofBuilderError {
412 fn new<E>(kind: BuilderErrorKind, source: E) -> Self
413 where
414 E: Into<Box<dyn std::error::Error + Send + Sync>>,
415 {
416 Self {
417 kind,
418 source: Some(source.into()),
419 }
420 }
421
422 fn cover(uncovered: QueryIdx, kinds: &[TranscriptCommitmentKind]) -> Self {
423 Self {
424 kind: BuilderErrorKind::Cover {
425 uncovered,
426 kinds: kinds.to_vec(),
427 },
428 source: None,
429 }
430 }
431}
432
433#[derive(Debug, PartialEq)]
434enum BuilderErrorKind {
435 Index,
436 MissingCommitment,
437 Cover {
438 uncovered: QueryIdx,
439 kinds: Vec<TranscriptCommitmentKind>,
440 },
441 NotSupported,
442}
443
444impl fmt::Display for TranscriptProofBuilderError {
445 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
446 f.write_str("transcript proof builder error: ")?;
447
448 match &self.kind {
449 BuilderErrorKind::Index => f.write_str("index error")?,
450 BuilderErrorKind::MissingCommitment => f.write_str("commitment error")?,
451 BuilderErrorKind::Cover { uncovered, kinds } => f.write_str(&format!(
452 "unable to cover the following ranges in transcript using available {:?} commitments: {uncovered}",
453 kinds
454 ))?,
455 BuilderErrorKind::NotSupported => f.write_str("not supported")?,
456 }
457
458 if let Some(source) = &self.source {
459 write!(f, " caused by: {}", source)?;
460 }
461
462 Ok(())
463 }
464}
465
466#[allow(clippy::single_range_in_vec_init)]
467#[cfg(test)]
468mod tests {
469 use rangeset::RangeSet;
470 use rstest::rstest;
471 use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON};
472
473 use crate::{
474 attestation::FieldId,
475 fixtures::{
476 attestation_fixture, encoder_secret, encoding_provider, request_fixture,
477 ConnectionFixture, RequestFixture,
478 },
479 hash::{Blake3, HashAlgId},
480 signing::SignatureAlgId,
481 transcript::TranscriptCommitConfigBuilder,
482 };
483
484 use super::*;
485
486 #[rstest]
487 fn test_verify_missing_encoding_commitment_root() {
488 let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
489 let connection = ConnectionFixture::tlsnotary(transcript.length());
490
491 let RequestFixture {
492 mut request,
493 encoding_tree,
494 } = request_fixture(
495 transcript.clone(),
496 encoding_provider(GET_WITH_HEADER, OK_JSON),
497 connection.clone(),
498 Blake3::default(),
499 Vec::new(),
500 );
501
502 let index = Index::default();
503 let mut builder = TranscriptProofBuilder::new(&transcript, Some(&encoding_tree), &index);
504
505 builder.reveal_recv(&(0..transcript.len().1)).unwrap();
506
507 let transcript_proof = builder.build().unwrap();
508
509 request.encoding_commitment_root = None;
510 let attestation = attestation_fixture(
511 request,
512 connection,
513 SignatureAlgId::SECP256K1,
514 encoder_secret(),
515 );
516
517 let provider = CryptoProvider::default();
518 let err = transcript_proof
519 .verify_with_provider(&provider, &attestation.body)
520 .err()
521 .unwrap();
522 assert!(matches!(err.kind, ErrorKind::Encoding));
523 }
524
525 #[rstest]
526 fn test_reveal_range_out_of_bounds() {
527 let transcript = Transcript::new(
528 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
529 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
530 );
531 let index = Index::default();
532 let mut builder = TranscriptProofBuilder::new(&transcript, None, &index);
533
534 let err = builder.reveal(&(10..15), Direction::Sent).unwrap_err();
535 assert!(matches!(err.kind, BuilderErrorKind::Index));
536
537 let err = builder
538 .reveal(&(10..15), Direction::Received)
539 .err()
540 .unwrap();
541 assert!(matches!(err.kind, BuilderErrorKind::Index));
542 }
543
544 #[rstest]
545 fn test_reveal_missing_encoding_tree() {
546 let transcript = Transcript::new(
547 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
548 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
549 );
550 let index = Index::default();
551 let mut builder = TranscriptProofBuilder::new(&transcript, None, &index);
552
553 let err = builder.reveal_recv(&(9..11)).unwrap_err();
554 assert!(matches!(err.kind, BuilderErrorKind::MissingCommitment));
555 }
556
557 #[rstest]
558 fn test_set_commitment_kinds_with_duplicates() {
559 let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
560 let index = Index::default();
561 let mut builder = TranscriptProofBuilder::new(&transcript, None, &index);
562 builder.commitment_kinds(&[
563 TranscriptCommitmentKind::Hash {
564 alg: HashAlgId::SHA256,
565 },
566 TranscriptCommitmentKind::Encoding,
567 TranscriptCommitmentKind::Hash {
568 alg: HashAlgId::SHA256,
569 },
570 TranscriptCommitmentKind::Hash {
571 alg: HashAlgId::SHA256,
572 },
573 TranscriptCommitmentKind::Encoding,
574 ]);
575
576 assert_eq!(
577 builder.commitment_kinds,
578 vec![
579 TranscriptCommitmentKind::Hash {
580 alg: HashAlgId::SHA256
581 },
582 TranscriptCommitmentKind::Encoding
583 ]
584 );
585 }
586
587 #[rstest]
588 #[case::reveal_all_rangesets_with_exact_set(
589 vec![RangeSet::from([0..10]), RangeSet::from([12..30]), RangeSet::from([0..5, 15..30]), RangeSet::from([70..75, 85..100])],
590 RangeSet::from([0..10, 12..30]),
591 true,
592 )]
593 #[case::reveal_all_rangesets_with_superset_ranges(
594 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])],
595 RangeSet::from([0..4, 6..9]),
596 true,
597 )]
598 #[case::reveal_all_rangesets_with_superset_range(
599 vec![RangeSet::from([0..1, 2..4]), RangeSet::from([1..3]), RangeSet::from([1..9]), RangeSet::from([2..3])],
600 RangeSet::from([0..4]),
601 true,
602 )]
603 #[case::failed_to_reveal_with_superset_range_missing_within(
604 vec![RangeSet::from([0..20, 45..56]), RangeSet::from([80..120]), RangeSet::from([50..53])],
605 RangeSet::from([0..120]),
606 false,
607 )]
608 #[case::failed_to_reveal_with_superset_range_missing_outside(
609 vec![RangeSet::from([2..20, 45..116]), RangeSet::from([20..45]), RangeSet::from([50..53])],
610 RangeSet::from([0..120]),
611 false,
612 )]
613 #[case::failed_to_reveal_with_superset_ranges_missing_outside(
614 vec![RangeSet::from([1..10]), RangeSet::from([1..20]), RangeSet::from([15..20, 75..110])],
615 RangeSet::from([0..41, 74..100]),
616 false,
617 )]
618 #[case::failed_to_reveal_as_no_subset_range(
619 vec![RangeSet::from([2..4]), RangeSet::from([1..2]), RangeSet::from([1..9]), RangeSet::from([2..3])],
620 RangeSet::from([0..1]),
621 false,
622 )]
623 #[allow(clippy::single_range_in_vec_init)]
624 fn test_reveal_mutliple_rangesets_with_one_rangeset(
625 #[case] commit_recv_rangesets: Vec<RangeSet<usize>>,
626 #[case] reveal_recv_rangeset: RangeSet<usize>,
627 #[case] success: bool,
628 ) {
629 let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
630
631 let mut transcript_commitment_builder = TranscriptCommitConfigBuilder::new(&transcript);
633 for rangeset in commit_recv_rangesets.iter() {
634 transcript_commitment_builder.commit_recv(rangeset).unwrap();
635 }
636
637 let transcripts_commitment_config = transcript_commitment_builder.build().unwrap();
638
639 let encoding_tree = EncodingTree::new(
640 &Blake3::default(),
641 transcripts_commitment_config.iter_encoding(),
642 &encoding_provider(GET_WITH_HEADER, OK_JSON),
643 &transcript.length(),
644 )
645 .unwrap();
646
647 let index = Index::default();
648 let mut builder = TranscriptProofBuilder::new(&transcript, Some(&encoding_tree), &index);
649
650 if success {
651 assert!(builder.reveal_recv(&reveal_recv_rangeset).is_ok());
652 } else {
653 let err = builder.reveal_recv(&reveal_recv_rangeset).unwrap_err();
654 assert!(matches!(err.kind, BuilderErrorKind::MissingCommitment));
655 }
656
657 let mut transcript_commitment_builder = TranscriptCommitConfigBuilder::new(&transcript);
659 transcript_commitment_builder.default_kind(TranscriptCommitmentKind::Hash {
660 alg: HashAlgId::SHA256,
661 });
662 for rangeset in commit_recv_rangesets.iter() {
663 transcript_commitment_builder.commit_recv(rangeset).unwrap();
664 }
665 let transcripts_commitment_config = transcript_commitment_builder.build().unwrap();
666
667 let plaintext_hash_secrets: Index<PlaintextHashSecret> = transcripts_commitment_config
668 .iter_hash()
669 .map(|(&(direction, ref idx), _)| PlaintextHashSecret {
670 direction,
671 idx: idx.clone(),
672 commitment: FieldId::default(),
673 blinder: rand::random(),
674 })
675 .collect::<Vec<_>>()
676 .into();
677 let mut builder = TranscriptProofBuilder::new(&transcript, None, &plaintext_hash_secrets);
678 builder.commitment_kinds(&[TranscriptCommitmentKind::Hash {
679 alg: HashAlgId::SHA256,
680 }]);
681
682 if success {
683 assert!(builder.reveal_recv(&reveal_recv_rangeset).is_ok());
684 } else {
685 let err = builder.reveal_recv(&reveal_recv_rangeset).unwrap_err();
686 assert!(matches!(err.kind, BuilderErrorKind::MissingCommitment));
687 }
688 }
689
690 #[rstest]
691 fn test_reveal_commitments_from_different_kinds() {
692 let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
693 let encoding_rangeset = RangeSet::from(1..6);
695 let mut transcript_commitment_builder = TranscriptCommitConfigBuilder::new(&transcript);
696 transcript_commitment_builder
697 .commit_recv(&encoding_rangeset)
698 .unwrap();
699 let hash_rangeset = RangeSet::from(9..12);
701 transcript_commitment_builder.default_kind(TranscriptCommitmentKind::Hash {
702 alg: HashAlgId::SHA256,
703 });
704 transcript_commitment_builder
705 .commit_recv(&hash_rangeset)
706 .unwrap();
707
708 let transcripts_commitment_config = transcript_commitment_builder.build().unwrap();
709
710 let encoding_tree = EncodingTree::new(
711 &Blake3::default(),
712 transcripts_commitment_config.iter_encoding(),
713 &encoding_provider(GET_WITH_HEADER, OK_JSON),
714 &transcript.length(),
715 )
716 .unwrap();
717
718 let plaintext_hash_secrets: Index<PlaintextHashSecret> = transcripts_commitment_config
719 .iter_hash()
720 .map(|(&(direction, ref idx), _)| PlaintextHashSecret {
721 direction,
722 idx: idx.clone(),
723 commitment: FieldId::default(),
724 blinder: rand::random(),
725 })
726 .collect::<Vec<_>>()
727 .into();
728
729 let mut builder =
730 TranscriptProofBuilder::new(&transcript, Some(&encoding_tree), &plaintext_hash_secrets);
731 builder.commitment_kinds(&[
732 TranscriptCommitmentKind::Hash {
733 alg: HashAlgId::BLAKE3,
734 },
735 TranscriptCommitmentKind::Encoding,
736 ]);
737
738 assert!(builder.reveal_recv(&encoding_rangeset).is_ok());
740 assert!(builder.reveal_recv(&hash_rangeset).is_ok());
742 }
743
744 #[rstest]
745 #[case::cover(
746 vec![RangeSet::from([1..5, 6..10])],
747 vec![RangeSet::from([2..4, 8..10])],
748 RangeSet::from([1..5, 6..10]),
749 RangeSet::from([2..4, 8..10]),
750 RangeSet::default(),
751 RangeSet::default(),
752 )]
753 #[case::failed_to_cover_sent(
754 vec![RangeSet::from([1..5, 6..10])],
755 vec![RangeSet::from([2..4, 8..10])],
756 RangeSet::from([1..5]),
757 RangeSet::from([2..4, 8..10]),
758 RangeSet::from([1..5]),
759 RangeSet::default(),
760 )]
761 #[case::failed_to_cover_recv(
762 vec![RangeSet::from([1..5, 6..10])],
763 vec![RangeSet::from([2..4, 8..10])],
764 RangeSet::from([1..5, 6..10]),
765 RangeSet::from([2..4]),
766 RangeSet::default(),
767 RangeSet::from([2..4]),
768 )]
769 #[case::failed_to_cover_both(
770 vec![RangeSet::from([1..5, 6..10])],
771 vec![RangeSet::from([2..4, 8..10])],
772 RangeSet::from([1..5]),
773 RangeSet::from([2..4]),
774 RangeSet::from([1..5]),
775 RangeSet::from([2..4]),
776 )]
777 #[allow(clippy::single_range_in_vec_init)]
778 fn test_transcript_proof_builder(
779 #[case] commit_sent_rangesets: Vec<RangeSet<usize>>,
780 #[case] commit_recv_rangesets: Vec<RangeSet<usize>>,
781 #[case] reveal_sent_rangeset: RangeSet<usize>,
782 #[case] reveal_recv_rangeset: RangeSet<usize>,
783 #[case] uncovered_sent_rangeset: RangeSet<usize>,
784 #[case] uncovered_recv_rangeset: RangeSet<usize>,
785 ) {
786 let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
787
788 let mut transcript_commitment_builder = TranscriptCommitConfigBuilder::new(&transcript);
790 for rangeset in commit_sent_rangesets.iter() {
791 transcript_commitment_builder.commit_sent(rangeset).unwrap();
792 }
793 for rangeset in commit_recv_rangesets.iter() {
794 transcript_commitment_builder.commit_recv(rangeset).unwrap();
795 }
796
797 let transcripts_commitment_config = transcript_commitment_builder.build().unwrap();
798
799 let encoding_tree = EncodingTree::new(
800 &Blake3::default(),
801 transcripts_commitment_config.iter_encoding(),
802 &encoding_provider(GET_WITH_HEADER, OK_JSON),
803 &transcript.length(),
804 )
805 .unwrap();
806
807 let index = Index::default();
808 let mut builder = TranscriptProofBuilder::new(&transcript, Some(&encoding_tree), &index);
809 builder.reveal_sent(&reveal_sent_rangeset).unwrap();
810 builder.reveal_recv(&reveal_recv_rangeset).unwrap();
811
812 if uncovered_sent_rangeset.is_empty() && uncovered_recv_rangeset.is_empty() {
813 assert!(builder.build().is_ok());
814 } else {
815 let TranscriptProofBuilderError { kind, .. } = builder.build().unwrap_err();
816 match kind {
817 BuilderErrorKind::Cover { uncovered, .. } => {
818 if !uncovered_sent_rangeset.is_empty() {
819 assert_eq!(uncovered.sent, Idx(uncovered_sent_rangeset));
820 }
821 if !uncovered_recv_rangeset.is_empty() {
822 assert_eq!(uncovered.recv, Idx(uncovered_recv_rangeset));
823 }
824 }
825 _ => panic!("unexpected error kind: {:?}", kind),
826 }
827 }
828 }
829}