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, 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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
29#[serde(try_from = "validation::EncodingProofUnchecked")]
30pub struct EncodingProof {
31 pub(super) inclusion_proof: MerkleProof,
34 pub(super) openings: HashMap<usize, Opening>,
35}
36
37impl EncodingProof {
38 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 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() > 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 leaves.push((*id, hasher.hash(&expected_leaf)));
114
115 auth.union_mut(idx.as_range_set());
116 }
117
118 inclusion_proof.verify(hasher, &commitment.root, leaves)?;
124
125 Ok((Idx(auth_sent), Idx(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 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}