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, EncodingCommitment},
12 Direction, Idx,
13 },
14};
15
16#[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#[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 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 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 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 leaves.push((*id, hasher.hash(&expected_leaf)));
113
114 auth.union_mut(idx.as_range_set());
115 }
116
117 inclusion_proof.verify(hasher, &commitment.root, leaves)?;
123
124 Ok((Idx(auth_sent), Idx(auth_recv)))
125 }
126}
127
128#[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#[derive(Debug, thiserror::Error)]
184#[error("invalid encoding proof: {0}")]
185pub struct InvalidEncodingProof(&'static str);
186
187mod validation {
188 use super::*;
189
190 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}