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