# commit.rs - source
Source of the Rust file `crates/core/src/transcript/commit.rs`.

Skip to main content

#
tlsn_core/transcript/
commit.rs

    1//! Transcript commitments.
    2
    3use std::{collections::HashSet, fmt};
    4
    5use rangeset::iter::{FromRangeIterator, IntoRangeIterator};
    6use serde::{Deserialize, Serialize};
    7
    8use crate::{
    9    hash::HashAlgId,
    10    transcript::{
    11        hash::{PlaintextHash, PlaintextHashSecret},
    12        Direction, RangeSet, Transcript,
    13    },
    14};
    15
    16/// Kind of transcript commitment.
    17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
    18#[non_exhaustive]
    19pub enum TranscriptCommitmentKind {
    20    /// A hash commitment to plaintext in the transcript.
    21    Hash {
    22        /// The hash algorithm used.
    23        alg: HashAlgId,
    24    },
    25}
    26
    27impl fmt::Display for TranscriptCommitmentKind {
    28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    29        match self {
    30            Self::Hash { alg } => write!(f, "hash ({alg})"),
    31        }
    32    }
    33}
    34
    35/// Transcript commitment.
    36#[derive(Debug, Clone, Serialize, Deserialize)]
    37#[non_exhaustive]
    38pub enum TranscriptCommitment {
    39    /// Plaintext hash commitment.
    40    Hash(PlaintextHash),
    41}
    42
    43/// Secret for a transcript commitment.
    44#[derive(Debug, Clone, Serialize, Deserialize)]
    45#[non_exhaustive]
    46pub enum TranscriptSecret {
    47    /// Plaintext hash secret.
    48    Hash(PlaintextHashSecret),
    49}
    50
    51/// Configuration for transcript commitments.
    52#[derive(Debug, Clone, Serialize, Deserialize)]
    53pub struct TranscriptCommitConfig {
    54    commits: Vec<((Direction, RangeSet<usize>), TranscriptCommitmentKind)>,
    55}
    56
    57impl TranscriptCommitConfig {
    58    /// Creates a new commit config builder.
    59    pub fn builder(transcript: &Transcript) -> TranscriptCommitConfigBuilder<'_> {
    60        TranscriptCommitConfigBuilder::new(transcript)
    61    }
    62
    63    /// Returns `true` if the configuration has any hash commitments.
    64    pub fn has_hash(&self) -> bool {
    65        self.commits
    66            .iter()
    67            .any(|(_, kind)| matches!(kind, TranscriptCommitmentKind::Hash { .. }))
    68    }
    69
    70    /// Returns an iterator over the hash commitment indices.
    71    pub fn iter_hash(&self) -> impl Iterator<Item = (&(Direction, RangeSet<usize>), &HashAlgId)> {
    72        self.commits.iter().map(|(idx, kind)| match kind {
    73            TranscriptCommitmentKind::Hash { alg } => (idx, alg),
    74        })
    75    }
    76
    77    /// Returns a request for the transcript commitments.
    78    pub fn to_request(&self) -> TranscriptCommitRequest {
    79        TranscriptCommitRequest {
    80            hash: self
    81                .iter_hash()
    82                .map(|((dir, idx), alg)| (*dir, idx.clone(), *alg))
    83                .collect(),
    84        }
    85    }
    86}
    87
    88/// A builder for [`TranscriptCommitConfig`].
    89#[derive(Debug)]
    90pub struct TranscriptCommitConfigBuilder<'a> {
    91    transcript: &'a Transcript,
    92    default_kind: TranscriptCommitmentKind,
    93    commits: HashSet<((Direction, RangeSet<usize>), TranscriptCommitmentKind)>,
    94}
    95
    96impl<'a> TranscriptCommitConfigBuilder<'a> {
    97    /// Creates a new commit config builder.
    98    pub fn new(transcript: &'a Transcript) -> Self {
    99        Self {
    100            transcript,
    101            default_kind: TranscriptCommitmentKind::Hash {
    102                alg: HashAlgId::BLAKE3,
    103            },
    104            commits: HashSet::default(),
    105        }
    106    }
    107
    108    /// Sets the default kind of commitment to use.
    109    pub fn default_kind(&mut self, default_kind: TranscriptCommitmentKind) -> &mut Self {
    110        self.default_kind = default_kind;
    111        self
    112    }
    113
    114    /// Adds a commitment.
    115    ///
    116    /// # Arguments
    117    ///
    118    /// * `ranges` - The ranges of the commitment.
    119    /// * `direction` - The direction of the transcript.
    120    /// * `kind` - The kind of commitment.
    121    pub fn commit_with_kind(
    122        &mut self,
    123        ranges: impl IntoRangeIterator<usize>,
    124        direction: Direction,
    125        kind: TranscriptCommitmentKind,
    126    ) -> Result<&mut Self, TranscriptCommitConfigBuilderError> {
    127        self.commit_with_kind_inner(RangeSet::from_range_iter(ranges), direction, kind)
    128    }
    129
    130    fn commit_with_kind_inner(
    131        &mut self,
    132        idx: RangeSet<usize>,
    133        direction: Direction,
    134        kind: TranscriptCommitmentKind,
    135    ) -> Result<&mut Self, TranscriptCommitConfigBuilderError> {
    136        if idx.end().unwrap_or(0) > self.transcript.len_of_direction(direction) {
    137            return Err(TranscriptCommitConfigBuilderError::new(
    138                ErrorKind::Index,
    139                format!(
    140                    "range is out of bounds of the transcript ({}): {} > {}",
    141                    direction,
    142                    idx.end().unwrap_or(0),
    143                    self.transcript.len_of_direction(direction)
    144                ),
    145            ));
    146        }
    147
    148        self.commits.insert(((direction, idx), kind));
    149
    150        Ok(self)
    151    }
    152
    153    /// Adds a commitment with the default kind.
    154    ///
    155    /// # Arguments
    156    ///
    157    /// * `ranges` - The ranges of the commitment.
    158    /// * `direction` - The direction of the transcript.
    159    pub fn commit(
    160        &mut self,
    161        ranges: impl IntoRangeIterator<usize>,
    162        direction: Direction,
    163    ) -> Result<&mut Self, TranscriptCommitConfigBuilderError> {
    164        self.commit_with_kind_inner(
    165            RangeSet::from_range_iter(ranges),
    166            direction,
    167            self.default_kind,
    168        )
    169    }
    170
    171    /// Adds a commitment with the default kind to the sent data transcript.
    172    ///
    173    /// # Arguments
    174    ///
    175    /// * `ranges` - The ranges of the commitment.
    176    pub fn commit_sent(
    177        &mut self,
    178        ranges: impl IntoRangeIterator<usize>,
    179    ) -> Result<&mut Self, TranscriptCommitConfigBuilderError> {
    180        self.commit_with_kind_inner(
    181            RangeSet::from_range_iter(ranges),
    182            Direction::Sent,
    183            self.default_kind,
    184        )
    185    }
    186
    187    /// Adds a commitment with the default kind to the received data transcript.
    188    ///
    189    /// # Arguments
    190    ///
    191    /// * `ranges` - The ranges of the commitment.
    192    pub fn commit_recv(
    193        &mut self,
    194        ranges: impl IntoRangeIterator<usize>,
    195    ) -> Result<&mut Self, TranscriptCommitConfigBuilderError> {
    196        self.commit_with_kind_inner(
    197            RangeSet::from_range_iter(ranges),
    198            Direction::Received,
    199            self.default_kind,
    200        )
    201    }
    202
    203    /// Builds the configuration.
    204    pub fn build(self) -> Result<TranscriptCommitConfig, TranscriptCommitConfigBuilderError> {
    205        Ok(TranscriptCommitConfig {
    206            commits: Vec::from_iter(self.commits),
    207        })
    208    }
    209}
    210
    211/// Error for [`TranscriptCommitConfigBuilder`].
    212#[derive(Debug, thiserror::Error)]
    213pub struct TranscriptCommitConfigBuilderError {
    214    kind: ErrorKind,
    215    source: Option<Box<dyn std::error::Error + Send + Sync>>,
    216}
    217
    218impl TranscriptCommitConfigBuilderError {
    219    fn new<E>(kind: ErrorKind, source: E) -> Self
    220    where
    221        E: Into<Box<dyn std::error::Error + Send + Sync>>,
    222    {
    223        Self {
    224            kind,
    225            source: Some(source.into()),
    226        }
    227    }
    228}
    229
    230#[derive(Debug)]
    231enum ErrorKind {
    232    Index,
    233}
    234
    235impl fmt::Display for TranscriptCommitConfigBuilderError {
    236    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    237        match self.kind {
    238            ErrorKind::Index => f.write_str("index error")?,
    239        }
    240
    241        if let Some(source) = &self.source {
    242            write!(f, " caused by: {source}")?;
    243        }
    244
    245        Ok(())
    246    }
    247}
    248
    249/// Request to compute transcript commitments.
    250#[derive(Debug, Clone, Serialize, Deserialize)]
    251pub struct TranscriptCommitRequest {
    252    hash: Vec<(Direction, RangeSet<usize>, HashAlgId)>,
    253}
    254
    255impl TranscriptCommitRequest {
    256    /// Returns `true` if a hash commitment is requested.
    257    pub fn has_hash(&self) -> bool {
    258        !self.hash.is_empty()
    259    }
    260
    261    /// Returns an iterator over the hash commitments.
    262    pub fn iter_hash(&self) -> impl Iterator<Item = &(Direction, RangeSet<usize>, HashAlgId)> {
    263        self.hash.iter()
    264    }
    265}
    266
    267#[cfg(test)]
    268mod tests {
    269    use super::*;
    270
    271    #[test]
    272    fn test_range_out_of_bounds() {
    273        let transcript = Transcript::new(
    274            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
    275            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
    276        );
    277        let mut builder = TranscriptCommitConfigBuilder::new(&transcript);
    278
    279        assert!(builder.commit_sent(&(10..15)).is_err());
    280        assert!(builder.commit_recv(&(10..15)).is_err());
    281    }
    282}