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 {kinds:?} commitments: {uncovered}"
550 ))?,
551 BuilderErrorKind::NotSupported => f.write_str("not supported")?,
552 }
553
554 if let Some(source) = &self.source {
555 write!(f, " caused by: {source}")?;
556 }
557
558 Ok(())
559 }
560}
561
562#[allow(clippy::single_range_in_vec_init)]
563#[cfg(test)]
564mod tests {
565 use rand::{Rng, SeedableRng};
566 use rangeset::RangeSet;
567 use rstest::rstest;
568 use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON};
569
570 use crate::{
571 fixtures::{encoding_provider, request_fixture, ConnectionFixture, RequestFixture},
572 hash::{Blake3, Blinder, HashAlgId},
573 transcript::TranscriptCommitConfigBuilder,
574 };
575
576 use super::*;
577
578 #[rstest]
579 fn test_verify_missing_encoding_commitment_root() {
580 let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
581 let connection = ConnectionFixture::tlsnotary(transcript.length());
582
583 let RequestFixture { encoding_tree, .. } = request_fixture(
584 transcript.clone(),
585 encoding_provider(GET_WITH_HEADER, OK_JSON),
586 connection.clone(),
587 Blake3::default(),
588 Vec::new(),
589 );
590
591 let secrets = vec![TranscriptSecret::Encoding(encoding_tree)];
592 let mut builder = TranscriptProofBuilder::new(&transcript, &secrets);
593
594 builder.reveal_recv(&(0..transcript.len().1)).unwrap();
595
596 let transcript_proof = builder.build().unwrap();
597
598 let provider = CryptoProvider::default();
599 let err = transcript_proof
600 .verify_with_provider(&provider, &transcript.length(), &[])
601 .err()
602 .unwrap();
603
604 assert!(matches!(err.kind, ErrorKind::Encoding));
605 }
606
607 #[rstest]
608 fn test_reveal_range_out_of_bounds() {
609 let transcript = Transcript::new(
610 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
611 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
612 );
613 let mut builder = TranscriptProofBuilder::new(&transcript, &[]);
614
615 let err = builder.reveal(&(10..15), Direction::Sent).unwrap_err();
616 assert!(matches!(err.kind, BuilderErrorKind::Index));
617
618 let err = builder
619 .reveal(&(10..15), Direction::Received)
620 .err()
621 .unwrap();
622 assert!(matches!(err.kind, BuilderErrorKind::Index));
623 }
624
625 #[rstest]
626 fn test_reveal_missing_encoding_tree() {
627 let transcript = Transcript::new(
628 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
629 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
630 );
631 let mut builder = TranscriptProofBuilder::new(&transcript, &[]);
632
633 let err = builder.reveal_recv(&(9..11)).unwrap_err();
634 assert!(matches!(err.kind, BuilderErrorKind::MissingCommitment));
635 }
636
637 #[rstest]
638 fn test_reveal_with_hash_commitment() {
639 let mut rng = rand::rngs::StdRng::seed_from_u64(0);
640 let provider = CryptoProvider::default();
641 let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
642
643 let direction = Direction::Sent;
644 let idx = Idx::new(0..10);
645 let blinder: Blinder = rng.random();
646 let alg = HashAlgId::SHA256;
647 let hasher = provider.hash.get(&alg).unwrap();
648
649 let commitment = PlaintextHash {
650 direction,
651 idx: idx.clone(),
652 hash: hash_plaintext(hasher, &transcript.sent()[0..10], &blinder),
653 };
654
655 let secret = PlaintextHashSecret {
656 direction,
657 idx: idx.clone(),
658 alg,
659 blinder,
660 };
661
662 let secrets = vec![TranscriptSecret::Hash(secret)];
663 let mut builder = TranscriptProofBuilder::new(&transcript, &secrets);
664
665 builder.reveal_sent(&(0..10)).unwrap();
666
667 let transcript_proof = builder.build().unwrap();
668
669 let partial_transcript = transcript_proof
670 .verify_with_provider(
671 &provider,
672 &transcript.length(),
673 &[TranscriptCommitment::Hash(commitment)],
674 )
675 .unwrap();
676
677 assert_eq!(
678 partial_transcript.sent_unsafe()[0..10],
679 transcript.sent()[0..10]
680 );
681 }
682
683 #[rstest]
684 fn test_reveal_with_inconsistent_hash_commitment() {
685 let mut rng = rand::rngs::StdRng::seed_from_u64(0);
686 let provider = CryptoProvider::default();
687 let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
688
689 let direction = Direction::Sent;
690 let idx = Idx::new(0..10);
691 let blinder: Blinder = rng.random();
692 let alg = HashAlgId::SHA256;
693 let hasher = provider.hash.get(&alg).unwrap();
694
695 let commitment = PlaintextHash {
696 direction,
697 idx: idx.clone(),
698 hash: hash_plaintext(hasher, &transcript.sent()[0..10], &blinder),
699 };
700
701 let secret = PlaintextHashSecret {
702 direction,
703 idx: idx.clone(),
704 alg,
705 blinder: rng.random(),
707 };
708
709 let secrets = vec![TranscriptSecret::Hash(secret)];
710 let mut builder = TranscriptProofBuilder::new(&transcript, &secrets);
711
712 builder.reveal_sent(&(0..10)).unwrap();
713
714 let transcript_proof = builder.build().unwrap();
715
716 let err = transcript_proof
717 .verify_with_provider(
718 &provider,
719 &transcript.length(),
720 &[TranscriptCommitment::Hash(commitment)],
721 )
722 .unwrap_err();
723
724 assert!(matches!(err.kind, ErrorKind::Hash));
725 }
726
727 #[rstest]
728 fn test_set_commitment_kinds_with_duplicates() {
729 let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
730 let mut builder = TranscriptProofBuilder::new(&transcript, &[]);
731 builder.commitment_kinds(&[
732 TranscriptCommitmentKind::Hash {
733 alg: HashAlgId::SHA256,
734 },
735 TranscriptCommitmentKind::Encoding,
736 TranscriptCommitmentKind::Hash {
737 alg: HashAlgId::SHA256,
738 },
739 TranscriptCommitmentKind::Hash {
740 alg: HashAlgId::SHA256,
741 },
742 TranscriptCommitmentKind::Encoding,
743 ]);
744
745 assert_eq!(
746 builder.commitment_kinds,
747 vec![
748 TranscriptCommitmentKind::Hash {
749 alg: HashAlgId::SHA256
750 },
751 TranscriptCommitmentKind::Encoding
752 ]
753 );
754 }
755
756 #[rstest]
757 #[case::reveal_all_rangesets_with_exact_set(
758 vec![RangeSet::from([0..10]), RangeSet::from([12..30]), RangeSet::from([0..5, 15..30]), RangeSet::from([70..75, 85..100])],
759 RangeSet::from([0..10, 12..30]),
760 true,
761 )]
762 #[case::reveal_all_rangesets_with_superset_ranges(
763 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])],
764 RangeSet::from([0..4, 6..9]),
765 true,
766 )]
767 #[case::reveal_all_rangesets_with_superset_range(
768 vec![RangeSet::from([0..1, 2..4]), RangeSet::from([1..3]), RangeSet::from([1..9]), RangeSet::from([2..3])],
769 RangeSet::from([0..4]),
770 true,
771 )]
772 #[case::failed_to_reveal_with_superset_range_missing_within(
773 vec![RangeSet::from([0..20, 45..56]), RangeSet::from([80..120]), RangeSet::from([50..53])],
774 RangeSet::from([0..120]),
775 false,
776 )]
777 #[case::failed_to_reveal_with_superset_range_missing_outside(
778 vec![RangeSet::from([2..20, 45..116]), RangeSet::from([20..45]), RangeSet::from([50..53])],
779 RangeSet::from([0..120]),
780 false,
781 )]
782 #[case::failed_to_reveal_with_superset_ranges_missing_outside(
783 vec![RangeSet::from([1..10]), RangeSet::from([1..20]), RangeSet::from([15..20, 75..110])],
784 RangeSet::from([0..41, 74..100]),
785 false,
786 )]
787 #[case::failed_to_reveal_as_no_subset_range(
788 vec![RangeSet::from([2..4]), RangeSet::from([1..2]), RangeSet::from([1..9]), RangeSet::from([2..3])],
789 RangeSet::from([0..1]),
790 false,
791 )]
792 #[allow(clippy::single_range_in_vec_init)]
793 fn test_reveal_mutliple_rangesets_with_one_rangeset(
794 #[case] commit_recv_rangesets: Vec<RangeSet<usize>>,
795 #[case] reveal_recv_rangeset: RangeSet<usize>,
796 #[case] success: bool,
797 ) {
798 let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
799
800 let mut transcript_commitment_builder = TranscriptCommitConfigBuilder::new(&transcript);
802 for rangeset in commit_recv_rangesets.iter() {
803 transcript_commitment_builder.commit_recv(rangeset).unwrap();
804 }
805
806 let transcripts_commitment_config = transcript_commitment_builder.build().unwrap();
807
808 let encoding_tree = EncodingTree::new(
809 &Blake3::default(),
810 transcripts_commitment_config.iter_encoding(),
811 &encoding_provider(GET_WITH_HEADER, OK_JSON),
812 )
813 .unwrap();
814
815 let secrets = vec![TranscriptSecret::Encoding(encoding_tree)];
816 let mut builder = TranscriptProofBuilder::new(&transcript, &secrets);
817
818 if success {
819 assert!(builder.reveal_recv(&reveal_recv_rangeset).is_ok());
820 } else {
821 let err = builder.reveal_recv(&reveal_recv_rangeset).unwrap_err();
822 assert!(matches!(err.kind, BuilderErrorKind::MissingCommitment));
823 }
824 }
825
826 #[rstest]
827 #[case::cover(
828 vec![RangeSet::from([1..5, 6..10])],
829 vec![RangeSet::from([2..4, 8..10])],
830 RangeSet::from([1..5, 6..10]),
831 RangeSet::from([2..4, 8..10]),
832 RangeSet::default(),
833 RangeSet::default(),
834 )]
835 #[case::failed_to_cover_sent(
836 vec![RangeSet::from([1..5, 6..10])],
837 vec![RangeSet::from([2..4, 8..10])],
838 RangeSet::from([1..5]),
839 RangeSet::from([2..4, 8..10]),
840 RangeSet::from([1..5]),
841 RangeSet::default(),
842 )]
843 #[case::failed_to_cover_recv(
844 vec![RangeSet::from([1..5, 6..10])],
845 vec![RangeSet::from([2..4, 8..10])],
846 RangeSet::from([1..5, 6..10]),
847 RangeSet::from([2..4]),
848 RangeSet::default(),
849 RangeSet::from([2..4]),
850 )]
851 #[case::failed_to_cover_both(
852 vec![RangeSet::from([1..5, 6..10])],
853 vec![RangeSet::from([2..4, 8..10])],
854 RangeSet::from([1..5]),
855 RangeSet::from([2..4]),
856 RangeSet::from([1..5]),
857 RangeSet::from([2..4]),
858 )]
859 #[allow(clippy::single_range_in_vec_init)]
860 fn test_transcript_proof_builder(
861 #[case] commit_sent_rangesets: Vec<RangeSet<usize>>,
862 #[case] commit_recv_rangesets: Vec<RangeSet<usize>>,
863 #[case] reveal_sent_rangeset: RangeSet<usize>,
864 #[case] reveal_recv_rangeset: RangeSet<usize>,
865 #[case] uncovered_sent_rangeset: RangeSet<usize>,
866 #[case] uncovered_recv_rangeset: RangeSet<usize>,
867 ) {
868 let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
869
870 let mut transcript_commitment_builder = TranscriptCommitConfigBuilder::new(&transcript);
872 for rangeset in commit_sent_rangesets.iter() {
873 transcript_commitment_builder.commit_sent(rangeset).unwrap();
874 }
875 for rangeset in commit_recv_rangesets.iter() {
876 transcript_commitment_builder.commit_recv(rangeset).unwrap();
877 }
878
879 let transcripts_commitment_config = transcript_commitment_builder.build().unwrap();
880
881 let encoding_tree = EncodingTree::new(
882 &Blake3::default(),
883 transcripts_commitment_config.iter_encoding(),
884 &encoding_provider(GET_WITH_HEADER, OK_JSON),
885 )
886 .unwrap();
887
888 let secrets = vec![TranscriptSecret::Encoding(encoding_tree)];
889 let mut builder = TranscriptProofBuilder::new(&transcript, &secrets);
890 builder.reveal_sent(&reveal_sent_rangeset).unwrap();
891 builder.reveal_recv(&reveal_recv_rangeset).unwrap();
892
893 if uncovered_sent_rangeset.is_empty() && uncovered_recv_rangeset.is_empty() {
894 assert!(builder.build().is_ok());
895 } else {
896 let TranscriptProofBuilderError { kind, .. } = builder.build().unwrap_err();
897 match kind {
898 BuilderErrorKind::Cover { uncovered, .. } => {
899 if !uncovered_sent_rangeset.is_empty() {
900 assert_eq!(uncovered.sent, Idx(uncovered_sent_rangeset));
901 }
902 if !uncovered_recv_rangeset.is_empty() {
903 assert_eq!(uncovered.recv, Idx(uncovered_recv_rangeset));
904 }
905 }
906 _ => panic!("unexpected error kind: {kind:?}"),
907 }
908 }
909 }
910}