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