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, EncoderSecret, EncodingCommitment},
12        Direction,
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: RangeSet<usize>,
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        secret: &EncoderSecret,
52        commitment: &EncodingCommitment,
53        sent: &[u8],
54        recv: &[u8],
55    ) -> Result<(RangeSet<usize>, RangeSet<usize>), EncodingProofError> {
56        let hasher = provider.get(&commitment.root.alg)?;
57
58        let encoder = new_encoder(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().unwrap_or(0) > 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().unwrap_or(0),
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);
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((auth_sent, 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::{encoding::EncodingTree, Transcript},
237    };
238
239    use super::*;
240
241    struct EncodingFixture {
242        transcript: Transcript,
243        proof: EncodingProof,
244        commitment: EncodingCommitment,
245    }
246
247    fn new_encoding_fixture() -> EncodingFixture {
248        let transcript = Transcript::new(POST_JSON, OK_JSON);
249
250        let idx_0 = (Direction::Sent, RangeSet::from(0..POST_JSON.len()));
251        let idx_1 = (Direction::Received, RangeSet::from(0..OK_JSON.len()));
252
253        let provider = encoding_provider(transcript.sent(), transcript.received());
254        let tree = EncodingTree::new(&Blake3::default(), [&idx_0, &idx_1], &provider).unwrap();
255
256        let proof = tree.proof([&idx_0, &idx_1].into_iter()).unwrap();
257
258        let commitment = EncodingCommitment { root: tree.root() };
259
260        EncodingFixture {
261            transcript,
262            proof,
263            commitment,
264        }
265    }
266
267    #[test]
268    fn test_verify_encoding_proof_tampered_seed() {
269        let EncodingFixture {
270            transcript,
271            proof,
272            commitment,
273        } = new_encoding_fixture();
274
275        let err = proof
276            .verify_with_provider(
277                &HashProvider::default(),
278                &encoder_secret_tampered_seed(),
279                &commitment,
280                transcript.sent(),
281                transcript.received(),
282            )
283            .unwrap_err();
284
285        assert!(matches!(err.kind, ErrorKind::Proof));
286    }
287
288    #[test]
289    fn test_verify_encoding_proof_out_of_range() {
290        let EncodingFixture {
291            transcript,
292            proof,
293            commitment,
294        } = new_encoding_fixture();
295
296        let sent = &transcript.sent()[transcript.sent().len() - 1..];
297        let recv = &transcript.received()[transcript.received().len() - 2..];
298
299        let err = proof
300            .verify_with_provider(
301                &HashProvider::default(),
302                &encoder_secret(),
303                &commitment,
304                sent,
305                recv,
306            )
307            .unwrap_err();
308
309        assert!(matches!(err.kind, ErrorKind::Proof));
310    }
311
312    #[test]
313    fn test_verify_encoding_proof_tampered_idx() {
314        let EncodingFixture {
315            transcript,
316            mut proof,
317            commitment,
318        } = new_encoding_fixture();
319
320        let Opening { idx, .. } = proof.openings.values_mut().next().unwrap();
321
322        *idx = RangeSet::from([0..3, 13..15]);
323
324        let err = proof
325            .verify_with_provider(
326                &HashProvider::default(),
327                &encoder_secret(),
328                &commitment,
329                transcript.sent(),
330                transcript.received(),
331            )
332            .unwrap_err();
333
334        assert!(matches!(err.kind, ErrorKind::Proof));
335    }
336
337    #[test]
338    fn test_verify_encoding_proof_tampered_encoding_blinder() {
339        let EncodingFixture {
340            transcript,
341            mut proof,
342            commitment,
343        } = new_encoding_fixture();
344
345        let Opening { blinder, .. } = proof.openings.values_mut().next().unwrap();
346
347        *blinder = rand::random();
348
349        let err = proof
350            .verify_with_provider(
351                &HashProvider::default(),
352                &encoder_secret(),
353                &commitment,
354                transcript.sent(),
355                transcript.received(),
356            )
357            .unwrap_err();
358
359        assert!(matches!(err.kind, ErrorKind::Proof));
360    }
361}