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