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