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