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