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