Skip to main content

tlsn_core/
transcript.rs

1//! Transcript types.
2//!
3//! All application data communicated over a TLS connection is referred to as a
4//! [`Transcript`]. A transcript is essentially just two vectors of bytes, each
5//! corresponding to a [`Direction`].
6//!
7//! TLS operates over a bidirectional byte stream, and thus there are no
8//! application layer semantics present in the transcript. For example, HTTPS is
9//! an application layer protocol that runs *over TLS* so there is no concept of
10//! "requests" or "responses" in the transcript itself. These semantics must be
11//! recovered by parsing the application data and relating it to the bytes
12//! in the transcript.
13//!
14//! ## Selective Disclosure
15//!
16//! Using a [`TranscriptProof`] a Prover can selectively disclose parts of a
17//! transcript to a Verifier in the form of a [`PartialTranscript`]. A Verifier
18//! always learns the length of the transcript, but sensitive data can be
19//! withheld.
20
21mod commit;
22pub mod hash;
23mod proof;
24#[cfg(test)]
25pub(crate) mod test;
26mod tls;
27
28use std::{fmt, ops::Range};
29
30use rangeset::{
31    iter::RangeIterator,
32    ops::{Index, Set},
33    set::RangeSet,
34};
35use serde::{Deserialize, Serialize};
36
37use crate::connection::TranscriptLength;
38
39pub use commit::{
40    TranscriptCommitConfig, TranscriptCommitConfigBuilder, TranscriptCommitConfigBuilderError,
41    TranscriptCommitRequest, TranscriptCommitment, TranscriptCommitmentKind, TranscriptSecret,
42};
43pub use proof::{
44    TranscriptProof, TranscriptProofBuilder, TranscriptProofBuilderError, TranscriptProofError,
45};
46pub use tls::{ContentType, Record, TlsTranscript, TlsTranscriptBuilder, TlsTranscriptError};
47
48/// A transcript contains the plaintext of all application data communicated
49/// between the Prover and the Server.
50#[derive(Clone, Serialize, Deserialize)]
51pub struct Transcript {
52    /// Data sent from the Prover to the Server.
53    sent: Vec<u8>,
54    /// Data received by the Prover from the Server.
55    received: Vec<u8>,
56}
57
58opaque_debug::implement!(Transcript);
59
60impl Transcript {
61    /// Creates a new transcript.
62    pub fn new(sent: impl Into<Vec<u8>>, received: impl Into<Vec<u8>>) -> Self {
63        Self {
64            sent: sent.into(),
65            received: received.into(),
66        }
67    }
68
69    /// Returns a reference to the sent data.
70    pub fn sent(&self) -> &[u8] {
71        &self.sent
72    }
73
74    /// Returns a reference to the received data.
75    pub fn received(&self) -> &[u8] {
76        &self.received
77    }
78
79    /// Returns the length of the sent and received data, respectively.
80    #[allow(clippy::len_without_is_empty)]
81    pub fn len(&self) -> (usize, usize) {
82        (self.sent.len(), self.received.len())
83    }
84
85    /// Returns the length of the transcript in the given direction.
86    pub(crate) fn len_of_direction(&self, direction: Direction) -> usize {
87        match direction {
88            Direction::Sent => self.sent.len(),
89            Direction::Received => self.received.len(),
90        }
91    }
92
93    /// Returns the transcript length.
94    pub fn length(&self) -> TranscriptLength {
95        TranscriptLength {
96            sent: self.sent.len() as u32,
97            received: self.received.len() as u32,
98        }
99    }
100
101    /// Returns the subsequence of the transcript with the provided index,
102    /// returning `None` if the index is out of bounds.
103    pub fn get(&self, direction: Direction, idx: &RangeSet<usize>) -> Option<Subsequence> {
104        let data = match direction {
105            Direction::Sent => &self.sent,
106            Direction::Received => &self.received,
107        };
108
109        if idx.end().unwrap_or(0) > data.len() {
110            return None;
111        }
112
113        Some(
114            Subsequence::new(
115                idx.clone(),
116                data.index(idx).fold(Vec::new(), |mut acc, s| {
117                    acc.extend_from_slice(s);
118                    acc
119                }),
120            )
121            .expect("data is same length as index"),
122        )
123    }
124
125    /// Returns a partial transcript containing the provided indices.
126    ///
127    /// # Panics
128    ///
129    /// Panics if the indices are out of bounds.
130    ///
131    /// # Arguments
132    ///
133    /// * `sent_idx` - The indices of the sent data to include.
134    /// * `recv_idx` - The indices of the received data to include.
135    pub fn to_partial(
136        &self,
137        sent_idx: RangeSet<usize>,
138        recv_idx: RangeSet<usize>,
139    ) -> PartialTranscript {
140        let sent_authed = sent_idx
141            .iter()
142            .flat_map(|range| self.sent[range].iter().copied())
143            .collect();
144        let received_authed = recv_idx
145            .iter()
146            .flat_map(|range| self.received[range].iter().copied())
147            .collect();
148
149        PartialTranscript {
150            sent_authed,
151            received_authed,
152            sent_idx,
153            recv_idx,
154            sent_total: self.sent.len(),
155            recv_total: self.received.len(),
156        }
157    }
158}
159
160/// A partial transcript.
161///
162/// A partial transcript is a transcript which may not have all the data
163/// authenticated.
164///
165/// Only authenticated data is stored: the bytes, and the set of ranges they
166/// occupy. The total length of each direction is also carried, but when the
167/// transcript comes from a peer it is untrusted — it is only ever compared
168/// against a caller-provided length, never used to size an allocation.
169#[derive(Debug, Clone, Serialize, Deserialize)]
170#[serde(try_from = "validation::PartialTranscriptUnchecked")]
171#[cfg_attr(test, derive(PartialEq))]
172pub struct PartialTranscript {
173    /// Sent data which has been authenticated.
174    sent_authed: Vec<u8>,
175    /// Received data which has been authenticated.
176    received_authed: Vec<u8>,
177    /// Index of `sent_authed`.
178    sent_idx: RangeSet<usize>,
179    /// Index of `received_authed`.
180    recv_idx: RangeSet<usize>,
181    /// Total bytelength of sent data in the original partial transcript.
182    sent_total: usize,
183    /// Total bytelength of received data in the original partial transcript.
184    recv_total: usize,
185}
186
187impl PartialTranscript {
188    /// Returns the index of sent data which have been authenticated.
189    pub fn sent_authed(&self) -> &RangeSet<usize> {
190        &self.sent_idx
191    }
192
193    /// Returns the index of received data which have been authenticated.
194    pub fn received_authed(&self) -> &RangeSet<usize> {
195        &self.recv_idx
196    }
197
198    /// Returns the sent data which have been authenticated.
199    pub(crate) fn sent_authed_bytes(&self) -> &[u8] {
200        &self.sent_authed
201    }
202
203    /// Returns the received data which have been authenticated.
204    pub(crate) fn received_authed_bytes(&self) -> &[u8] {
205        &self.received_authed
206    }
207
208    /// Returns the total length of the sent data.
209    #[allow(clippy::len_without_is_empty)]
210    pub fn len_sent(&self) -> usize {
211        self.sent_total
212    }
213
214    /// Returns the total length of the received data.
215    #[allow(clippy::len_without_is_empty)]
216    pub fn len_received(&self) -> usize {
217        self.recv_total
218    }
219
220    /// Unions the authenticated data of another compressed transcript into this
221    /// one.
222    ///
223    /// Where both transcripts authenticate the same position, `self` takes
224    /// precedence.
225    ///
226    /// # Panics
227    ///
228    /// Panics if the transcripts are not the same length.
229    pub fn union_transcript(&mut self, other: &PartialTranscript) {
230        assert_eq!(
231            self.sent_total, other.sent_total,
232            "sent data are not the same length"
233        );
234        assert_eq!(
235            self.recv_total, other.recv_total,
236            "received data are not the same length"
237        );
238
239        let (sent_idx, sent_authed) = overlay(
240            &self.sent_idx,
241            &self.sent_authed,
242            &other.sent_idx,
243            &other.sent_authed,
244        );
245        let (recv_idx, received_authed) = overlay(
246            &self.recv_idx,
247            &self.received_authed,
248            &other.recv_idx,
249            &other.received_authed,
250        );
251
252        self.sent_idx = sent_idx;
253        self.sent_authed = sent_authed;
254        self.recv_idx = recv_idx;
255        self.received_authed = received_authed;
256    }
257
258    /// Unions an authenticated subsequence into this transcript.
259    ///
260    /// Where the subsequence overlaps already authenticated data, the
261    /// subsequence takes precedence.
262    ///
263    /// # Panics
264    ///
265    /// Panics if the subsequence is out of bounds of the transcript.
266    pub fn union_subsequence(&mut self, direction: Direction, seq: &Subsequence) {
267        let total = match direction {
268            Direction::Sent => self.sent_total,
269            Direction::Received => self.recv_total,
270        };
271
272        if seq.index().end().unwrap_or(0) > total {
273            panic!("subsequence is out of bounds of the transcript");
274        }
275
276        match direction {
277            Direction::Sent => {
278                let (idx, data) =
279                    overlay(seq.index(), seq.data(), &self.sent_idx, &self.sent_authed);
280                self.sent_idx = idx;
281                self.sent_authed = data;
282            }
283            Direction::Received => {
284                let (idx, data) = overlay(
285                    seq.index(),
286                    seq.data(),
287                    &self.recv_idx,
288                    &self.received_authed,
289                );
290                self.recv_idx = idx;
291                self.received_authed = data;
292            }
293        }
294    }
295
296    /// Returns the authenticated bytes of the given transcript position range.
297    ///
298    /// Returns `None` if the range is not fully authenticated, i.e. it spans a
299    /// gap or more than one authenticated range.
300    pub(crate) fn locate(&self, direction: Direction, range: &Range<usize>) -> Option<&[u8]> {
301        let (idx, bytes) = match direction {
302            Direction::Sent => (&self.sent_idx, self.sent_authed_bytes()),
303            Direction::Received => (&self.recv_idx, self.received_authed_bytes()),
304        };
305
306        locate(idx, bytes, range)
307    }
308
309    /// Returns the sent data, materialized to `expected` bytes.
310    ///
311    /// # Warning
312    ///
313    /// Only the positions in [`sent_authed`](PartialTranscript::sent_authed)
314    /// have been authenticated; every other position is `0`.
315    ///
316    /// The allocation is sized by `expected`, a length the caller trusts (for
317    /// example the length recorded by the Verifier, or a signed
318    /// [`TranscriptLength`](crate::connection::TranscriptLength)), never by the
319    /// length carried by this transcript. Returns an error if `expected` does
320    /// not match the length carried by this transcript.
321    pub fn sent_unsafe(&self, expected: usize) -> Result<Vec<u8>, InvalidTranscriptLength> {
322        self.expand(Direction::Sent, expected)
323    }
324
325    /// Returns the received data, materialized to `expected` bytes.
326    ///
327    /// # Warning
328    ///
329    /// Only the positions in
330    /// [`received_authed`](PartialTranscript::received_authed) have been
331    /// authenticated; every other position is `0`.
332    ///
333    /// The allocation is sized by `expected`, a length the caller trusts, never
334    /// by the length carried by this transcript. Returns an error if `expected`
335    /// does not match the length carried by this transcript.
336    pub fn received_unsafe(&self, expected: usize) -> Result<Vec<u8>, InvalidTranscriptLength> {
337        self.expand(Direction::Received, expected)
338    }
339
340    fn expand(
341        &self,
342        direction: Direction,
343        expected: usize,
344    ) -> Result<Vec<u8>, InvalidTranscriptLength> {
345        let (idx, bytes, total) = match direction {
346            Direction::Sent => (&self.sent_idx, self.sent_authed_bytes(), self.sent_total),
347            Direction::Received => (
348                &self.recv_idx,
349                self.received_authed_bytes(),
350                self.recv_total,
351            ),
352        };
353
354        if total != expected {
355            return Err(InvalidTranscriptLength {
356                expected,
357                actual: total,
358            });
359        }
360
361        let mut out = vec![0; expected];
362        let mut offset = 0;
363        for range in idx.iter() {
364            out[range.clone()].copy_from_slice(&bytes[offset..offset + range.len()]);
365            offset += range.len();
366        }
367
368        Ok(out)
369    }
370
371    /// Materializes the bytes of `range` for a direction, filling
372    /// unauthenticated positions with `0`.
373    pub(crate) fn materialize_range(&self, direction: Direction, range: &Range<usize>) -> Vec<u8> {
374        let (idx, bytes) = match direction {
375            Direction::Sent => (&self.sent_idx, self.sent_authed_bytes()),
376            Direction::Received => (&self.recv_idx, self.received_authed_bytes()),
377        };
378
379        let mut out = Vec::with_capacity(range.len());
380        let mut pos = range.start;
381        let mut offset = 0;
382
383        for r in idx.iter() {
384            if r.end <= pos {
385                offset += r.len();
386                continue;
387            }
388            if r.start >= range.end {
389                break;
390            }
391
392            if pos < r.start {
393                let end = r.start.min(range.end);
394                out.resize(out.len() + (end - pos), 0);
395                pos = end;
396            }
397
398            let start = pos.max(r.start);
399            let end = r.end.min(range.end);
400            if start < end {
401                out.extend_from_slice(&bytes[offset + (start - r.start)..offset + (end - r.start)]);
402                pos = end;
403            }
404
405            offset += r.len();
406
407            if pos >= range.end {
408                break;
409            }
410        }
411
412        if pos < range.end {
413            out.resize(out.len() + (range.end - pos), 0);
414        }
415
416        out
417    }
418}
419
420/// Invalid transcript length error.
421#[derive(Debug, thiserror::Error)]
422#[error("invalid transcript length: expected {expected}, got {actual}")]
423pub struct InvalidTranscriptLength {
424    /// The expected length.
425    pub expected: usize,
426    /// The actual length.
427    pub actual: usize,
428}
429
430/// Locates the byte slice of `range` within `bytes`, where `bytes` is the
431/// concatenation of the authenticated positions of `idx` in ascending order.
432///
433/// Returns `None` if `range` is not fully authenticated.
434fn locate<'a>(idx: &RangeSet<usize>, bytes: &'a [u8], range: &Range<usize>) -> Option<&'a [u8]> {
435    let mut offset = 0;
436    for r in idx.iter() {
437        if range.start >= r.start && range.end <= r.end {
438            let start = offset + (range.start - r.start);
439            return Some(&bytes[start..start + range.len()]);
440        }
441        offset += r.len();
442    }
443    None
444}
445
446/// Overlays the authenticated data of `lose` onto `win`, with `win` taking
447/// precedence where both authenticate the same position.
448///
449/// Both inputs are `(index, bytes)` pairs where `bytes` is the concatenation of
450/// the authenticated positions of `index` in ascending order. The result is the
451/// same representation for the union of the two index sets.
452fn overlay(
453    win_idx: &RangeSet<usize>,
454    win_bytes: &[u8],
455    lose_idx: &RangeSet<usize>,
456    lose_bytes: &[u8],
457) -> (RangeSet<usize>, Vec<u8>) {
458    let delta = lose_idx.difference(win_idx).into_set();
459    let lose_delta = subset_bytes(lose_idx, lose_bytes, &delta);
460    merge_disjoint(win_idx, win_bytes, &delta, &lose_delta)
461}
462
463/// Returns the bytes of `idx` restricted to `subset`, concatenated in ascending
464/// order.
465///
466/// `subset` is assumed to be a subset of `idx`.
467fn subset_bytes(idx: &RangeSet<usize>, bytes: &[u8], subset: &RangeSet<usize>) -> Vec<u8> {
468    let mut out = Vec::new();
469    let mut offset = 0;
470    let mut sub = subset.iter().peekable();
471
472    for range in idx.iter() {
473        while let Some(s) = sub.peek().cloned() {
474            if s.end <= range.start {
475                sub.next();
476                continue;
477            }
478            if s.start >= range.end {
479                break;
480            }
481
482            let start = s.start.max(range.start);
483            let end = s.end.min(range.end);
484            out.extend_from_slice(
485                &bytes[offset + (start - range.start)..offset + (end - range.start)],
486            );
487
488            if s.end <= range.end {
489                sub.next();
490            } else {
491                break;
492            }
493        }
494
495        offset += range.len();
496    }
497
498    out
499}
500
501/// Merges two concatenated byte runs whose index sets are disjoint.
502fn merge_disjoint(
503    a_idx: &RangeSet<usize>,
504    a_bytes: &[u8],
505    b_idx: &RangeSet<usize>,
506    b_bytes: &[u8],
507) -> (RangeSet<usize>, Vec<u8>) {
508    let mut ranges = Vec::new();
509    let mut bytes = Vec::with_capacity(a_bytes.len() + b_bytes.len());
510
511    let mut a = a_idx.iter().peekable();
512    let mut b = b_idx.iter().peekable();
513    let mut ao = 0;
514    let mut bo = 0;
515
516    loop {
517        let take_a = match (a.peek(), b.peek()) {
518            (Some(a), Some(b)) => a.start < b.start,
519            (Some(_), None) => true,
520            (None, Some(_)) => false,
521            (None, None) => break,
522        };
523
524        if take_a {
525            let range = a.next().unwrap();
526            bytes.extend_from_slice(&a_bytes[ao..ao + range.len()]);
527            ao += range.len();
528            ranges.push(range);
529        } else {
530            let range = b.next().unwrap();
531            bytes.extend_from_slice(&b_bytes[bo..bo + range.len()]);
532            bo += range.len();
533            ranges.push(range);
534        }
535    }
536
537    (RangeSet::new_from_slice(&ranges), bytes)
538}
539
540impl PartialTranscript {
541    /// Creates a new partial transcript initialized to all 0s.
542    ///
543    /// # Arguments
544    ///
545    /// * `sent_len` - The length of the sent data.
546    /// * `received_len` - The length of the received data.
547    pub fn new(sent_len: usize, received_len: usize) -> Self {
548        Self {
549            sent_authed: Vec::new(),
550            received_authed: Vec::new(),
551            sent_idx: RangeSet::default(),
552            recv_idx: RangeSet::default(),
553            sent_total: sent_len,
554            recv_total: received_len,
555        }
556    }
557
558    /// Returns whether the transcript is complete.
559    pub fn is_complete(&self) -> bool {
560        self.sent_idx.len() == self.sent_total && self.recv_idx.len() == self.recv_total
561    }
562
563    /// Returns whether the index is in bounds of the transcript.
564    pub fn contains(&self, direction: Direction, idx: &RangeSet<usize>) -> bool {
565        match direction {
566            Direction::Sent => idx.end().unwrap_or(0) <= self.sent_total,
567            Direction::Received => idx.end().unwrap_or(0) <= self.recv_total,
568        }
569    }
570
571    /// Returns the index of sent data which haven't been authenticated.
572    pub fn sent_unauthed(&self) -> RangeSet<usize> {
573        (0..self.sent_total).difference(&self.sent_idx).into_set()
574    }
575
576    /// Returns the index of received data which haven't been authenticated.
577    pub fn received_unauthed(&self) -> RangeSet<usize> {
578        (0..self.recv_total).difference(&self.recv_idx).into_set()
579    }
580
581    /// Returns an iterator over the authenticated data in the transcript.
582    pub fn iter(&self, direction: Direction) -> impl Iterator<Item = u8> + '_ {
583        let bytes = match direction {
584            Direction::Sent => &self.sent_authed,
585            Direction::Received => &self.received_authed,
586        };
587
588        bytes.iter().copied()
589    }
590}
591
592/// The direction of data communicated over a TLS connection.
593///
594/// This is used to differentiate between data sent from the Prover to the TLS
595/// peer, and data received by the Prover from the TLS peer (client or server).
596#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
597pub enum Direction {
598    /// Sent from the Prover to the TLS peer.
599    Sent = 0x00,
600    /// Received by the prover from the TLS peer.
601    Received = 0x01,
602}
603
604impl fmt::Display for Direction {
605    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
606        match self {
607            Direction::Sent => write!(f, "sent"),
608            Direction::Received => write!(f, "received"),
609        }
610    }
611}
612
613/// Transcript subsequence.
614#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
615#[serde(try_from = "validation::SubsequenceUnchecked")]
616pub struct Subsequence {
617    /// Index of the subsequence.
618    idx: RangeSet<usize>,
619    /// Data of the subsequence.
620    data: Vec<u8>,
621}
622
623impl Subsequence {
624    /// Creates a new subsequence.
625    pub fn new(idx: RangeSet<usize>, data: Vec<u8>) -> Result<Self, InvalidSubsequence> {
626        if idx.len() != data.len() {
627            return Err(InvalidSubsequence(
628                "index length does not match data length",
629            ));
630        }
631
632        Ok(Self { idx, data })
633    }
634
635    /// Returns the index of the subsequence.
636    pub fn index(&self) -> &RangeSet<usize> {
637        &self.idx
638    }
639
640    /// Returns the data of the subsequence.
641    pub fn data(&self) -> &[u8] {
642        &self.data
643    }
644
645    /// Returns the length of the subsequence.
646    #[allow(clippy::len_without_is_empty)]
647    pub fn len(&self) -> usize {
648        self.data.len()
649    }
650
651    /// Returns the inner parts of the subsequence.
652    pub fn into_parts(self) -> (RangeSet<usize>, Vec<u8>) {
653        (self.idx, self.data)
654    }
655
656    /// Copies the subsequence data into the given destination.
657    ///
658    /// # Panics
659    ///
660    /// Panics if the subsequence ranges are out of bounds.
661    #[cfg(test)]
662    pub(crate) fn copy_to(&self, dest: &mut [u8]) {
663        let mut offset = 0;
664        for range in self.idx.iter() {
665            dest[range.clone()].copy_from_slice(&self.data[offset..offset + range.len()]);
666            offset += range.len();
667        }
668    }
669}
670
671/// Invalid subsequence error.
672#[derive(Debug, thiserror::Error)]
673#[error("invalid subsequence: {0}")]
674pub struct InvalidSubsequence(&'static str);
675
676mod validation {
677    use super::*;
678
679    #[derive(Debug, Deserialize)]
680    pub(super) struct SubsequenceUnchecked {
681        idx: RangeSet<usize>,
682        data: Vec<u8>,
683    }
684
685    impl TryFrom<SubsequenceUnchecked> for Subsequence {
686        type Error = InvalidSubsequence;
687
688        fn try_from(unchecked: SubsequenceUnchecked) -> Result<Self, Self::Error> {
689            Self::new(unchecked.idx, unchecked.data)
690        }
691    }
692
693    /// Invalid partial transcript error.
694    #[derive(Debug, thiserror::Error)]
695    #[error("invalid partial transcript: {0}")]
696    pub struct InvalidPartialTranscript(&'static str);
697
698    #[derive(Debug, Deserialize)]
699    #[cfg_attr(test, derive(Serialize))]
700    pub(super) struct PartialTranscriptUnchecked {
701        sent_authed: Vec<u8>,
702        received_authed: Vec<u8>,
703        sent_idx: RangeSet<usize>,
704        recv_idx: RangeSet<usize>,
705        sent_total: usize,
706        recv_total: usize,
707    }
708
709    impl TryFrom<PartialTranscriptUnchecked> for PartialTranscript {
710        type Error = InvalidPartialTranscript;
711
712        fn try_from(unchecked: PartialTranscriptUnchecked) -> Result<Self, Self::Error> {
713            if unchecked.sent_authed.len() != unchecked.sent_idx.len()
714                || unchecked.received_authed.len() != unchecked.recv_idx.len()
715            {
716                return Err(InvalidPartialTranscript(
717                    "lengths of index and data don't match",
718                ));
719            }
720
721            if unchecked.sent_idx.end().unwrap_or(0) > unchecked.sent_total
722                || unchecked.recv_idx.end().unwrap_or(0) > unchecked.recv_total
723            {
724                return Err(InvalidPartialTranscript(
725                    "ranges are not in bounds of the data",
726                ));
727            }
728
729            Ok(Self {
730                received_authed: unchecked.received_authed,
731                recv_idx: unchecked.recv_idx,
732                recv_total: unchecked.recv_total,
733                sent_authed: unchecked.sent_authed,
734                sent_idx: unchecked.sent_idx,
735                sent_total: unchecked.sent_total,
736            })
737        }
738    }
739
740    #[cfg(test)]
741    mod tests {
742        use rstest::{fixture, rstest};
743
744        use super::*;
745
746        #[fixture]
747        fn partial_transcript() -> PartialTranscriptUnchecked {
748            PartialTranscriptUnchecked {
749                received_authed: vec![1, 2, 3, 11, 12, 13],
750                sent_authed: vec![4, 5, 6, 14, 15, 16],
751                recv_idx: RangeSet::from([1..4, 11..14]),
752                sent_idx: RangeSet::from([4..7, 14..17]),
753                sent_total: 20,
754                recv_total: 20,
755            }
756        }
757
758        #[rstest]
759        fn test_partial_transcript_valid(partial_transcript: PartialTranscriptUnchecked) {
760            let bytes = bincode::serialize(&partial_transcript).unwrap();
761            let transcript: Result<PartialTranscript, Box<bincode::ErrorKind>> =
762                bincode::deserialize(&bytes);
763            assert!(transcript.is_ok());
764        }
765
766        #[rstest]
767        // Expect to fail since the length of data and the length of the index
768        // do not match.
769        fn test_partial_transcript_invalid_lengths(
770            mut partial_transcript: PartialTranscriptUnchecked,
771        ) {
772            // Add an extra byte to the data.
773            let mut old = partial_transcript.sent_authed;
774            old.extend([1]);
775            partial_transcript.sent_authed = old;
776
777            let bytes = bincode::serialize(&partial_transcript).unwrap();
778            let transcript: Result<PartialTranscript, Box<bincode::ErrorKind>> =
779                bincode::deserialize(&bytes);
780            assert!(transcript.is_err());
781        }
782
783        #[rstest]
784        // Expect to fail since the index is out of bounds.
785        fn test_partial_transcript_invalid_ranges(
786            mut partial_transcript: PartialTranscriptUnchecked,
787        ) {
788            // Change the total to be less than the last range's end bound.
789            let end = partial_transcript.sent_idx.iter().next_back().unwrap().end;
790
791            partial_transcript.sent_total = end - 1;
792
793            let bytes = bincode::serialize(&partial_transcript).unwrap();
794            let transcript: Result<PartialTranscript, Box<bincode::ErrorKind>> =
795                bincode::deserialize(&bytes);
796            assert!(transcript.is_err());
797        }
798
799        #[rstest]
800        // A peer can declare an arbitrarily large total. Parsing must not
801        // allocate from it (this used to abort via `vec![0; total]`), and
802        // materializing against a trusted length must refuse rather than size
803        // the allocation by the declared total.
804        fn test_partial_transcript_huge_total_does_not_allocate() {
805            let unchecked = PartialTranscriptUnchecked {
806                received_authed: Vec::new(),
807                sent_authed: Vec::new(),
808                recv_idx: RangeSet::default(),
809                sent_idx: RangeSet::default(),
810                sent_total: 1 << 62,
811                recv_total: 0,
812            };
813            let bytes = bincode::serialize(&unchecked).unwrap();
814
815            let transcript: PartialTranscript = bincode::deserialize(&bytes).unwrap();
816            assert_eq!(transcript.len_sent(), 1 << 62);
817
818            // A mismatched expected length is refused; no allocation happens.
819            assert!(transcript.sent_unsafe(12).is_err());
820        }
821
822        #[rstest]
823        // Deserializing a transcript embedded in a proof must not abort on an
824        // oversized declared total either.
825        fn test_transcript_proof_huge_total_does_not_allocate() {
826            let unchecked = PartialTranscriptUnchecked {
827                received_authed: Vec::new(),
828                sent_authed: Vec::new(),
829                recv_idx: RangeSet::default(),
830                sent_idx: RangeSet::default(),
831                sent_total: 1 << 62,
832                recv_total: 0,
833            };
834            let bytes = bincode::serialize(&unchecked).unwrap();
835
836            // The trailing `hash_secrets` field is missing, so this errors; the
837            // point is that it returns rather than aborting.
838            let proof: Result<TranscriptProof, Box<bincode::ErrorKind>> =
839                bincode::deserialize(&bytes);
840            assert!(proof.is_err());
841        }
842    }
843}
844
845#[cfg(test)]
846mod tests {
847    use rstest::{fixture, rstest};
848
849    use super::*;
850
851    fn sent_bytes(partial: &PartialTranscript) -> Vec<u8> {
852        partial.sent_unsafe(partial.len_sent()).unwrap()
853    }
854
855    fn recv_bytes(partial: &PartialTranscript) -> Vec<u8> {
856        partial.received_unsafe(partial.len_received()).unwrap()
857    }
858
859    #[fixture]
860    fn transcript() -> Transcript {
861        Transcript::new(
862            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
863            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
864        )
865    }
866
867    #[fixture]
868    fn partial_transcript() -> PartialTranscript {
869        transcript().to_partial(RangeSet::from([1..4, 6..9]), RangeSet::from([2..5, 7..10]))
870    }
871
872    #[rstest]
873    fn test_transcript_get_subsequence(transcript: Transcript) {
874        let subseq = transcript
875            .get(Direction::Received, &RangeSet::from([0..4, 7..10]))
876            .unwrap();
877        assert_eq!(subseq.data, vec![0, 1, 2, 3, 7, 8, 9]);
878
879        let subseq = transcript
880            .get(Direction::Sent, &RangeSet::from([0..4, 9..12]))
881            .unwrap();
882        assert_eq!(subseq.data, vec![0, 1, 2, 3, 9, 10, 11]);
883
884        let subseq = transcript.get(Direction::Received, &RangeSet::from([0..4, 7..10, 11..13]));
885        assert_eq!(subseq, None);
886
887        let subseq = transcript.get(Direction::Sent, &RangeSet::from([0..4, 7..10, 11..13]));
888        assert_eq!(subseq, None);
889    }
890
891    #[rstest]
892    fn test_partial_transcript_serialization_ok(partial_transcript: PartialTranscript) {
893        let bytes = bincode::serialize(&partial_transcript).unwrap();
894        let deserialized_transcript: PartialTranscript = bincode::deserialize(&bytes).unwrap();
895        assert_eq!(partial_transcript, deserialized_transcript);
896    }
897
898    #[rstest]
899    fn test_transcript_to_partial_success(transcript: Transcript) {
900        let partial = transcript.to_partial(RangeSet::from(0..2), RangeSet::from(3..7));
901        assert_eq!(sent_bytes(&partial), [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
902        assert_eq!(recv_bytes(&partial), [0, 0, 0, 3, 4, 5, 6, 0, 0, 0, 0, 0]);
903    }
904
905    #[rstest]
906    #[should_panic]
907    fn test_transcript_to_partial_failure(transcript: Transcript) {
908        let _ = transcript.to_partial(RangeSet::from(0..14), RangeSet::from(3..7));
909    }
910
911    #[rstest]
912    fn test_partial_transcript_contains(transcript: Transcript) {
913        let partial = transcript.to_partial(RangeSet::from(0..2), RangeSet::from(3..7));
914        assert!(partial.contains(Direction::Sent, &RangeSet::from([0..5, 7..10])));
915        assert!(!partial.contains(Direction::Received, &RangeSet::from([4..6, 7..13])))
916    }
917
918    #[rstest]
919    fn test_partial_transcript_unauthed(transcript: Transcript) {
920        let partial = transcript.to_partial(RangeSet::from(0..2), RangeSet::from(3..7));
921        assert_eq!(partial.sent_unauthed(), RangeSet::from(2..12));
922        assert_eq!(partial.received_unauthed(), RangeSet::from([0..3, 7..12]));
923    }
924
925    #[rstest]
926    fn test_partial_transcript_union_success(transcript: Transcript) {
927        // Non overlapping ranges.
928        let mut simple_partial = transcript.to_partial(RangeSet::from(0..2), RangeSet::from(3..7));
929
930        let other_simple_partial =
931            transcript.to_partial(RangeSet::from(3..5), RangeSet::from(1..2));
932
933        simple_partial.union_transcript(&other_simple_partial);
934
935        assert_eq!(
936            sent_bytes(&simple_partial),
937            [0, 1, 0, 3, 4, 0, 0, 0, 0, 0, 0, 0]
938        );
939        assert_eq!(
940            recv_bytes(&simple_partial),
941            [0, 1, 0, 3, 4, 5, 6, 0, 0, 0, 0, 0]
942        );
943        assert_eq!(simple_partial.sent_authed(), &RangeSet::from([0..2, 3..5]));
944        assert_eq!(
945            simple_partial.received_authed(),
946            &RangeSet::from([1..2, 3..7])
947        );
948
949        // Overwrite with another partial transcript.
950
951        let another_simple_partial =
952            transcript.to_partial(RangeSet::from(1..4), RangeSet::from(6..9));
953
954        simple_partial.union_transcript(&another_simple_partial);
955
956        assert_eq!(
957            sent_bytes(&simple_partial),
958            [0, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0]
959        );
960        assert_eq!(
961            recv_bytes(&simple_partial),
962            [0, 1, 0, 3, 4, 5, 6, 7, 8, 0, 0, 0]
963        );
964        assert_eq!(simple_partial.sent_authed(), &RangeSet::from(0..5));
965        assert_eq!(
966            simple_partial.received_authed(),
967            &RangeSet::from([1..2, 3..9])
968        );
969
970        // Overlapping ranges.
971        let mut overlap_partial = transcript.to_partial(RangeSet::from(4..6), RangeSet::from(3..7));
972
973        let other_overlap_partial =
974            transcript.to_partial(RangeSet::from(3..5), RangeSet::from(5..9));
975
976        overlap_partial.union_transcript(&other_overlap_partial);
977
978        assert_eq!(
979            sent_bytes(&overlap_partial),
980            [0, 0, 0, 3, 4, 5, 0, 0, 0, 0, 0, 0]
981        );
982        assert_eq!(
983            recv_bytes(&overlap_partial),
984            [0, 0, 0, 3, 4, 5, 6, 7, 8, 0, 0, 0]
985        );
986        assert_eq!(overlap_partial.sent_authed(), &RangeSet::from([3..5, 4..6]));
987        assert_eq!(
988            overlap_partial.received_authed(),
989            &RangeSet::from([3..7, 5..9])
990        );
991
992        // Equal ranges.
993        let mut equal_partial = transcript.to_partial(RangeSet::from(4..6), RangeSet::from(3..7));
994
995        let other_equal_partial = transcript.to_partial(RangeSet::from(4..6), RangeSet::from(3..7));
996
997        equal_partial.union_transcript(&other_equal_partial);
998
999        assert_eq!(
1000            sent_bytes(&equal_partial),
1001            [0, 0, 0, 0, 4, 5, 0, 0, 0, 0, 0, 0]
1002        );
1003        assert_eq!(
1004            recv_bytes(&equal_partial),
1005            [0, 0, 0, 3, 4, 5, 6, 0, 0, 0, 0, 0]
1006        );
1007        assert_eq!(equal_partial.sent_authed(), &RangeSet::from(4..6));
1008        assert_eq!(equal_partial.received_authed(), &RangeSet::from(3..7));
1009
1010        // Subset ranges.
1011        let mut subset_partial =
1012            transcript.to_partial(RangeSet::from(4..10), RangeSet::from(3..11));
1013
1014        let other_subset_partial =
1015            transcript.to_partial(RangeSet::from(6..9), RangeSet::from(5..6));
1016
1017        subset_partial.union_transcript(&other_subset_partial);
1018
1019        assert_eq!(
1020            sent_bytes(&subset_partial),
1021            [0, 0, 0, 0, 4, 5, 6, 7, 8, 9, 0, 0]
1022        );
1023        assert_eq!(
1024            recv_bytes(&subset_partial),
1025            [0, 0, 0, 3, 4, 5, 6, 7, 8, 9, 10, 0]
1026        );
1027        assert_eq!(subset_partial.sent_authed(), &RangeSet::from(4..10));
1028        assert_eq!(subset_partial.received_authed(), &RangeSet::from(3..11));
1029    }
1030
1031    #[rstest]
1032    #[should_panic]
1033    fn test_partial_transcript_union_failure(transcript: Transcript) {
1034        let mut partial = transcript.to_partial(RangeSet::from(4..10), RangeSet::from(3..11));
1035
1036        let other_transcript = Transcript::new(
1037            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
1038            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
1039        );
1040
1041        let other_partial = other_transcript.to_partial(RangeSet::from(6..9), RangeSet::from(5..6));
1042
1043        partial.union_transcript(&other_partial);
1044    }
1045
1046    #[rstest]
1047    fn test_partial_transcript_union_subseq_success(transcript: Transcript) {
1048        let mut partial = transcript.to_partial(RangeSet::from(4..10), RangeSet::from(3..11));
1049        let sent_seq =
1050            Subsequence::new(RangeSet::from([0..3, 5..7]), [0, 1, 2, 5, 6].into()).unwrap();
1051        let recv_seq =
1052            Subsequence::new(RangeSet::from([0..4, 5..7]), [0, 1, 2, 3, 5, 6].into()).unwrap();
1053
1054        partial.union_subsequence(Direction::Sent, &sent_seq);
1055        partial.union_subsequence(Direction::Received, &recv_seq);
1056
1057        assert_eq!(sent_bytes(&partial), [0, 1, 2, 0, 4, 5, 6, 7, 8, 9, 0, 0]);
1058        assert_eq!(recv_bytes(&partial), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0]);
1059        assert_eq!(partial.sent_authed(), &RangeSet::from([0..3, 4..10]));
1060        assert_eq!(partial.received_authed(), &RangeSet::from(0..11));
1061
1062        // Overwrite with another subseq.
1063        let other_sent_seq = Subsequence::new(RangeSet::from(0..3), [3, 2, 1].into()).unwrap();
1064
1065        partial.union_subsequence(Direction::Sent, &other_sent_seq);
1066        assert_eq!(sent_bytes(&partial), [3, 2, 1, 0, 4, 5, 6, 7, 8, 9, 0, 0]);
1067        assert_eq!(partial.sent_authed(), &RangeSet::from([0..3, 4..10]));
1068    }
1069
1070    #[rstest]
1071    #[should_panic]
1072    fn test_partial_transcript_union_subseq_failure(transcript: Transcript) {
1073        let mut partial = transcript.to_partial(RangeSet::from(4..10), RangeSet::from(3..11));
1074
1075        let sent_seq =
1076            Subsequence::new(RangeSet::from([0..3, 13..15]), [0, 1, 2, 5, 6].into()).unwrap();
1077
1078        partial.union_subsequence(Direction::Sent, &sent_seq);
1079    }
1080
1081    #[rstest]
1082    #[should_panic]
1083    fn test_subsequence_new_invalid_len() {
1084        let _ = Subsequence::new(RangeSet::from([0..3, 5..8]), [0, 1, 2, 5, 6].into()).unwrap();
1085    }
1086
1087    #[rstest]
1088    #[should_panic]
1089    fn test_subsequence_copy_to_invalid_len() {
1090        let seq = Subsequence::new(RangeSet::from([0..3, 5..7]), [0, 1, 2, 5, 6].into()).unwrap();
1091
1092        let mut data: [u8; 3] = [0, 1, 2];
1093        seq.copy_to(&mut data);
1094    }
1095
1096    #[rstest]
1097    fn test_compressed_union(transcript: Transcript) {
1098        // self authenticates [4..10], other authenticates [0..3, 5..9].
1099        let mut a = transcript.to_partial(RangeSet::from(4..10), RangeSet::default());
1100        let b = transcript.to_partial(RangeSet::from([0..3, 5..9]), RangeSet::default());
1101
1102        a.union_transcript(&b);
1103
1104        // self wins on the overlap [5..9]; other only contributes [0..3].
1105        assert_eq!(a.sent_authed(), &RangeSet::from([0..3, 4..10]));
1106        assert_eq!(a.sent_authed_bytes(), &[0, 1, 2, 4, 5, 6, 7, 8, 9]);
1107    }
1108
1109    #[rstest]
1110    fn test_compressed_union_precedence() {
1111        // self authenticates [0..2] with [9, 9]; other authenticates [1..4]
1112        // with positions [1, 2, 3] = [1, 1, 1].
1113        let mut a = PartialTranscript {
1114            sent_authed: vec![9, 9],
1115            received_authed: Vec::new(),
1116            sent_idx: RangeSet::from(0..2),
1117            recv_idx: RangeSet::default(),
1118            sent_total: 4,
1119            recv_total: 0,
1120        };
1121        let b = PartialTranscript {
1122            sent_authed: vec![1, 1, 1],
1123            received_authed: Vec::new(),
1124            sent_idx: RangeSet::from(1..4),
1125            recv_idx: RangeSet::default(),
1126            sent_total: 4,
1127            recv_total: 0,
1128        };
1129
1130        a.union_transcript(&b);
1131
1132        // self keeps [0..2]; other contributes only [2..4].
1133        assert_eq!(a.sent_authed(), &RangeSet::from(0..4));
1134        assert_eq!(a.sent_authed_bytes(), &[9, 9, 1, 1]);
1135    }
1136
1137    #[rstest]
1138    fn test_compressed_union_subsequence(transcript: Transcript) {
1139        let mut compressed = transcript.to_partial(RangeSet::from(4..10), RangeSet::from(3..11));
1140
1141        let sent_seq =
1142            Subsequence::new(RangeSet::from([0..3, 5..7]), [0, 1, 2, 5, 6].into()).unwrap();
1143        let recv_seq =
1144            Subsequence::new(RangeSet::from([0..4, 5..7]), [0, 1, 2, 3, 5, 6].into()).unwrap();
1145
1146        compressed.union_subsequence(Direction::Sent, &sent_seq);
1147        compressed.union_subsequence(Direction::Received, &recv_seq);
1148
1149        assert_eq!(compressed.sent_authed(), &RangeSet::from([0..3, 4..10]));
1150        assert_eq!(compressed.sent_authed_bytes(), &[0, 1, 2, 4, 5, 6, 7, 8, 9]);
1151        assert_eq!(compressed.received_authed(), &RangeSet::from(0..11));
1152        assert_eq!(
1153            compressed.received_authed_bytes(),
1154            &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
1155        );
1156
1157        // Overwrite with another subseq.
1158        let other_sent_seq = Subsequence::new(RangeSet::from(0..3), [3, 2, 1].into()).unwrap();
1159        compressed.union_subsequence(Direction::Sent, &other_sent_seq);
1160
1161        assert_eq!(compressed.sent_authed(), &RangeSet::from([0..3, 4..10]));
1162        assert_eq!(compressed.sent_authed_bytes(), &[3, 2, 1, 4, 5, 6, 7, 8, 9]);
1163    }
1164
1165    #[rstest]
1166    #[should_panic]
1167    fn test_compressed_union_subsequence_out_of_bounds(transcript: Transcript) {
1168        let mut compressed = transcript.to_partial(RangeSet::from(4..10), RangeSet::from(3..11));
1169
1170        let sent_seq =
1171            Subsequence::new(RangeSet::from([0..3, 13..15]), [0, 1, 2, 5, 6].into()).unwrap();
1172
1173        compressed.union_subsequence(Direction::Sent, &sent_seq);
1174    }
1175
1176    #[rstest]
1177    fn test_compressed_locate(transcript: Transcript) {
1178        let compressed = transcript.to_partial(RangeSet::from([1..4, 6..9]), RangeSet::default());
1179
1180        // Fully authenticated.
1181        assert_eq!(
1182            compressed.locate(Direction::Sent, &(2..4)),
1183            Some([2u8, 3].as_slice())
1184        );
1185        // Spans the gap between [1..4] and [6..9].
1186        assert_eq!(compressed.locate(Direction::Sent, &(3..7)), None);
1187        // Partially authenticated.
1188        assert_eq!(compressed.locate(Direction::Sent, &(0..2)), None);
1189        // Not authenticated at all.
1190        assert_eq!(compressed.locate(Direction::Sent, &(9..12)), None);
1191    }
1192
1193    #[rstest]
1194    fn test_compressed_sent_unsafe(transcript: Transcript) {
1195        let compressed = transcript.to_partial(RangeSet::from(4..8), RangeSet::from(3..5));
1196
1197        let sent = compressed.sent_unsafe(12).unwrap();
1198        assert_eq!(sent, [0, 0, 0, 0, 4, 5, 6, 7, 0, 0, 0, 0]);
1199        assert_eq!(sent.len(), 12);
1200
1201        let received = compressed.received_unsafe(12).unwrap();
1202        assert_eq!(received, [0, 0, 0, 3, 4, 0, 0, 0, 0, 0, 0, 0]);
1203
1204        // A mismatched length is refused rather than sized by the transcript.
1205        assert!(compressed.sent_unsafe(11).is_err());
1206        assert!(compressed.sent_unsafe(1 << 62).is_err());
1207    }
1208
1209    #[rstest]
1210    fn test_materialize_range_fills_gaps(transcript: Transcript) {
1211        let compressed = transcript.to_partial(RangeSet::from([1..3, 6..8]), RangeSet::default());
1212
1213        // Authenticated positions keep their bytes; gaps are zero.
1214        assert_eq!(
1215            compressed.materialize_range(Direction::Sent, &(0..9)),
1216            [0, 1, 2, 0, 0, 0, 6, 7, 0]
1217        );
1218        // A range fully inside a gap is all zeros.
1219        assert_eq!(
1220            compressed.materialize_range(Direction::Sent, &(3..6)),
1221            [0, 0, 0]
1222        );
1223    }
1224}