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