tlsn_core/transcript/encoding/
proof.rs

1use std::{collections::HashMap, fmt};
2
3use rangeset::{RangeSet, UnionMut};
4use serde::{Deserialize, Serialize};
5
6use crate::{
7    hash::{Blinder, HashProviderError},
8    merkle::{MerkleError, MerkleProof},
9    transcript::{
10        commit::MAX_TOTAL_COMMITTED_DATA,
11        encoding::{new_encoder, Encoder, EncodingCommitment},
12        Direction, Idx,
13    },
14    CryptoProvider,
15};
16
17/// An opening of a leaf in the encoding tree.
18#[derive(Clone, Serialize, Deserialize)]
19pub(super) struct Opening {
20    pub(super) direction: Direction,
21    pub(super) idx: Idx,
22    pub(super) blinder: Blinder,
23}
24
25opaque_debug::implement!(Opening);
26
27/// An encoding commitment proof.
28#[derive(Debug, Clone, Serialize, Deserialize)]
29#[serde(try_from = "validation::EncodingProofUnchecked")]
30pub struct EncodingProof {
31    /// The proof of inclusion of the commitment(s) in the Merkle tree of
32    /// commitments.
33    pub(super) inclusion_proof: MerkleProof,
34    pub(super) openings: HashMap<usize, Opening>,
35}
36
37impl EncodingProof {
38    /// Verifies the proof against the commitment.
39    ///
40    /// Returns the authenticated indices of the sent and received data,
41    /// respectively.
42    ///
43    /// # Arguments
44    ///
45    /// * `provider` - Crypto provider.
46    /// * `commitment` - Encoding commitment to verify against.
47    /// * `sent` - Sent data to authenticate.
48    /// * `recv` - Received data to authenticate.
49    pub fn verify_with_provider(
50        &self,
51        provider: &CryptoProvider,
52        commitment: &EncodingCommitment,
53        sent: &[u8],
54        recv: &[u8],
55    ) -> Result<(Idx, Idx), EncodingProofError> {
56        let hasher = provider.hash.get(&commitment.root.alg)?;
57
58        let encoder = new_encoder(&commitment.secret);
59        let Self {
60            inclusion_proof,
61            openings,
62        } = self;
63
64        let mut leaves = Vec::with_capacity(openings.len());
65        let mut expected_leaf = Vec::default();
66        let mut total_opened = 0u128;
67        let mut auth_sent = RangeSet::default();
68        let mut auth_recv = RangeSet::default();
69        for (
70            id,
71            Opening {
72                direction,
73                idx,
74                blinder,
75            },
76        ) in openings
77        {
78            // Make sure the amount of data being proved is bounded.
79            total_opened += idx.len() as u128;
80            if total_opened > MAX_TOTAL_COMMITTED_DATA as u128 {
81                return Err(EncodingProofError::new(
82                    ErrorKind::Proof,
83                    "exceeded maximum allowed data",
84                ))?;
85            }
86
87            let (data, auth) = match direction {
88                Direction::Sent => (sent, &mut auth_sent),
89                Direction::Received => (recv, &mut auth_recv),
90            };
91
92            // Make sure the ranges are within the bounds of the transcript.
93            if idx.end() > data.len() {
94                return Err(EncodingProofError::new(
95                    ErrorKind::Proof,
96                    format!(
97                        "index out of bounds of the transcript ({}): {} > {}",
98                        direction,
99                        idx.end(),
100                        data.len()
101                    ),
102                ));
103            }
104
105            expected_leaf.clear();
106            for range in idx.iter_ranges() {
107                encoder.encode_data(*direction, range.clone(), &data[range], &mut expected_leaf);
108            }
109            expected_leaf.extend_from_slice(blinder.as_bytes());
110
111            // Compute the expected hash of the commitment to make sure it is
112            // present in the merkle tree.
113            leaves.push((*id, hasher.hash(&expected_leaf)));
114
115            auth.union_mut(idx.as_range_set());
116        }
117
118        // Verify that the expected hashes are present in the merkle tree.
119        //
120        // This proves the Prover committed to the purported data prior to the encoder
121        // seed being revealed. Ergo, if the encodings are authentic then the purported
122        // data is authentic.
123        inclusion_proof.verify(hasher, &commitment.root, leaves)?;
124
125        Ok((Idx(auth_sent), Idx(auth_recv)))
126    }
127}
128
129/// Error for [`EncodingProof`].
130#[derive(Debug, thiserror::Error)]
131pub struct EncodingProofError {
132    kind: ErrorKind,
133    source: Option<Box<dyn std::error::Error + Send + Sync>>,
134}
135
136impl EncodingProofError {
137    fn new<E>(kind: ErrorKind, source: E) -> Self
138    where
139        E: Into<Box<dyn std::error::Error + Send + Sync>>,
140    {
141        Self {
142            kind,
143            source: Some(source.into()),
144        }
145    }
146}
147
148#[derive(Debug)]
149enum ErrorKind {
150    Provider,
151    Proof,
152}
153
154impl fmt::Display for EncodingProofError {
155    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156        f.write_str("encoding proof error: ")?;
157
158        match self.kind {
159            ErrorKind::Provider => f.write_str("provider error")?,
160            ErrorKind::Proof => f.write_str("proof error")?,
161        }
162
163        if let Some(source) = &self.source {
164            write!(f, " caused by: {source}")?;
165        }
166
167        Ok(())
168    }
169}
170
171impl From<HashProviderError> for EncodingProofError {
172    fn from(error: HashProviderError) -> Self {
173        Self::new(ErrorKind::Provider, error)
174    }
175}
176
177impl From<MerkleError> for EncodingProofError {
178    fn from(error: MerkleError) -> Self {
179        Self::new(ErrorKind::Proof, error)
180    }
181}
182
183/// Invalid encoding proof error.
184#[derive(Debug, thiserror::Error)]
185#[error("invalid encoding proof: {0}")]
186pub struct InvalidEncodingProof(&'static str);
187
188mod validation {
189    use super::*;
190
191    /// The maximum allowed height of the Merkle tree of encoding commitments.
192    ///
193    /// The statistical security parameter (SSP) of the encoding commitment
194    /// protocol is calculated as "the number of uniformly random bits in a
195    /// single bit's encoding minus `MAX_HEIGHT`".
196    ///
197    /// For example, a bit encoding used in garbled circuits typically has 127
198    /// uniformly random bits, hence when using it in the encoding
199    /// commitment protocol, the SSP is 127 - 30 = 97 bits.
200    ///
201    /// Leaving this validation here as a fail-safe in case we ever start
202    /// using shorter encodings.
203    const MAX_HEIGHT: usize = 30;
204
205    #[derive(Debug, Deserialize)]
206    pub(super) struct EncodingProofUnchecked {
207        inclusion_proof: MerkleProof,
208        openings: HashMap<usize, Opening>,
209    }
210
211    impl TryFrom<EncodingProofUnchecked> for EncodingProof {
212        type Error = InvalidEncodingProof;
213
214        fn try_from(unchecked: EncodingProofUnchecked) -> Result<Self, Self::Error> {
215            if unchecked.inclusion_proof.leaf_count() > 1 << MAX_HEIGHT {
216                return Err(InvalidEncodingProof(
217                    "the height of the tree exceeds the maximum allowed",
218                ));
219            }
220
221            Ok(Self {
222                inclusion_proof: unchecked.inclusion_proof,
223                openings: unchecked.openings,
224            })
225        }
226    }
227}
228
229#[cfg(test)]
230mod test {
231    use tlsn_data_fixtures::http::{request::POST_JSON, response::OK_JSON};
232
233    use crate::{
234        fixtures::{encoder_secret, encoder_secret_tampered_seed, encoding_provider},
235        hash::Blake3,
236        transcript::{
237            encoding::{EncoderSecret, EncodingTree},
238            Idx, Transcript,
239        },
240    };
241
242    use super::*;
243
244    struct EncodingFixture {
245        transcript: Transcript,
246        proof: EncodingProof,
247        commitment: EncodingCommitment,
248    }
249
250    fn new_encoding_fixture(secret: EncoderSecret) -> EncodingFixture {
251        let transcript = Transcript::new(POST_JSON, OK_JSON);
252
253        let idx_0 = (Direction::Sent, Idx::new(0..POST_JSON.len()));
254        let idx_1 = (Direction::Received, Idx::new(0..OK_JSON.len()));
255
256        let provider = encoding_provider(transcript.sent(), transcript.received());
257        let tree = EncodingTree::new(&Blake3::default(), [&idx_0, &idx_1], &provider).unwrap();
258
259        let proof = tree.proof([&idx_0, &idx_1].into_iter()).unwrap();
260
261        let commitment = EncodingCommitment {
262            root: tree.root(),
263            secret,
264        };
265
266        EncodingFixture {
267            transcript,
268            proof,
269            commitment,
270        }
271    }
272
273    #[test]
274    fn test_verify_encoding_proof_tampered_seed() {
275        let EncodingFixture {
276            transcript,
277            proof,
278            commitment,
279        } = new_encoding_fixture(encoder_secret_tampered_seed());
280
281        let err = proof
282            .verify_with_provider(
283                &CryptoProvider::default(),
284                &commitment,
285                transcript.sent(),
286                transcript.received(),
287            )
288            .unwrap_err();
289
290        assert!(matches!(err.kind, ErrorKind::Proof));
291    }
292
293    #[test]
294    fn test_verify_encoding_proof_out_of_range() {
295        let EncodingFixture {
296            transcript,
297            proof,
298            commitment,
299        } = new_encoding_fixture(encoder_secret());
300
301        let sent = &transcript.sent()[transcript.sent().len() - 1..];
302        let recv = &transcript.received()[transcript.received().len() - 2..];
303
304        let err = proof
305            .verify_with_provider(&CryptoProvider::default(), &commitment, sent, recv)
306            .unwrap_err();
307
308        assert!(matches!(err.kind, ErrorKind::Proof));
309    }
310
311    #[test]
312    fn test_verify_encoding_proof_tampered_idx() {
313        let EncodingFixture {
314            transcript,
315            mut proof,
316            commitment,
317        } = new_encoding_fixture(encoder_secret());
318
319        let Opening { idx, .. } = proof.openings.values_mut().next().unwrap();
320
321        *idx = Idx::new([0..3, 13..15]);
322
323        let err = proof
324            .verify_with_provider(
325                &CryptoProvider::default(),
326                &commitment,
327                transcript.sent(),
328                transcript.received(),
329            )
330            .unwrap_err();
331
332        assert!(matches!(err.kind, ErrorKind::Proof));
333    }
334
335    #[test]
336    fn test_verify_encoding_proof_tampered_encoding_blinder() {
337        let EncodingFixture {
338            transcript,
339            mut proof,
340            commitment,
341        } = new_encoding_fixture(encoder_secret());
342
343        let Opening { blinder, .. } = proof.openings.values_mut().next().unwrap();
344
345        *blinder = rand::random();
346
347        let err = proof
348            .verify_with_provider(
349                &CryptoProvider::default(),
350                &commitment,
351                transcript.sent(),
352                transcript.received(),
353            )
354            .unwrap_err();
355
356        assert!(matches!(err.kind, ErrorKind::Proof));
357    }
358}