tlsn_core/transcript/encoding/
proof.rs1use 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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
28#[serde(try_from = "validation::EncodingProofUnchecked")]
29pub struct EncodingProof {
30 pub(super) inclusion_proof: MerkleProof,
33 pub(super) openings: HashMap<usize, Opening>,
34}
35
36impl EncodingProof {
37 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 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 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 leaves.push((*id, hasher.hash(&expected_leaf)));
114
115 auth.union_mut(idx);
116 }
117
118 inclusion_proof.verify(hasher, &commitment.root, leaves)?;
124
125 Ok((auth_sent, auth_recv))
126 }
127}
128
129#[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#[derive(Debug, thiserror::Error)]
185#[error("invalid encoding proof: {0}")]
186pub struct InvalidEncodingProof(&'static str);
187
188mod validation {
189 use super::*;
190
191 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}