tlsn_core/transcript/encoding/
encoder.rs

1use std::ops::Range;
2
3use crate::transcript::Direction;
4use itybity::ToBits;
5use rand::{RngCore, SeedableRng};
6use rand_chacha::ChaCha12Rng;
7use serde::{Deserialize, Serialize};
8
9/// The size of the encoding for 1 bit, in bytes.
10const BIT_ENCODING_SIZE: usize = 16;
11/// The size of the encoding for 1 byte, in bytes.
12const BYTE_ENCODING_SIZE: usize = 128;
13
14/// Secret used by an encoder to generate encodings.
15#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
16pub struct EncoderSecret {
17    seed: [u8; 32],
18    delta: [u8; BIT_ENCODING_SIZE],
19}
20
21opaque_debug::implement!(EncoderSecret);
22
23impl EncoderSecret {
24    /// Creates a new secret.
25    ///
26    /// # Arguments
27    ///
28    /// * `seed` - The seed for the PRG.
29    /// * `delta` - Delta for deriving the one-encodings.
30    pub fn new(seed: [u8; 32], delta: [u8; 16]) -> Self {
31        Self { seed, delta }
32    }
33
34    /// Returns the seed.
35    pub fn seed(&self) -> &[u8; 32] {
36        &self.seed
37    }
38
39    /// Returns the delta.
40    pub fn delta(&self) -> &[u8; 16] {
41        &self.delta
42    }
43}
44
45/// Creates a new encoder.
46pub fn new_encoder(secret: &EncoderSecret) -> impl Encoder {
47    ChaChaEncoder::new(secret)
48}
49
50pub(crate) struct ChaChaEncoder {
51    seed: [u8; 32],
52    delta: [u8; 16],
53}
54
55impl ChaChaEncoder {
56    pub(crate) fn new(secret: &EncoderSecret) -> Self {
57        let seed = *secret.seed();
58        let delta = *secret.delta();
59
60        Self { seed, delta }
61    }
62
63    pub(crate) fn new_prg(&self, stream_id: u64) -> ChaCha12Rng {
64        let mut prg = ChaCha12Rng::from_seed(self.seed);
65        prg.set_stream(stream_id);
66        prg.set_word_pos(0);
67        prg
68    }
69}
70
71/// A transcript encoder.
72///
73/// This is an internal implementation detail that should not be exposed to the
74/// public API.
75pub trait Encoder {
76    /// Writes the zero encoding for the given range of the transcript into the
77    /// destination buffer.
78    fn encode_range(&self, direction: Direction, range: Range<usize>, dest: &mut Vec<u8>);
79
80    /// Writes the encoding for the given data into the destination buffer.
81    fn encode_data(
82        &self,
83        direction: Direction,
84        range: Range<usize>,
85        data: &[u8],
86        dest: &mut Vec<u8>,
87    );
88}
89
90impl Encoder for ChaChaEncoder {
91    fn encode_range(&self, direction: Direction, range: Range<usize>, dest: &mut Vec<u8>) {
92        // ChaCha encoder works with 32-bit words. Each encoded bit is 128 bits long.
93        const WORDS_PER_BYTE: u128 = 8 * 128 / 32;
94
95        let stream_id: u64 = match direction {
96            Direction::Sent => 0,
97            Direction::Received => 1,
98        };
99
100        let mut prg = self.new_prg(stream_id);
101        let len = range.len() * BYTE_ENCODING_SIZE;
102        let pos = dest.len();
103
104        // Write 0s to the destination buffer.
105        dest.resize(pos + len, 0);
106
107        // Fill the destination buffer with the PRG.
108        prg.set_word_pos(range.start as u128 * WORDS_PER_BYTE);
109        prg.fill_bytes(&mut dest[pos..pos + len]);
110    }
111
112    fn encode_data(
113        &self,
114        direction: Direction,
115        range: Range<usize>,
116        data: &[u8],
117        dest: &mut Vec<u8>,
118    ) {
119        const ZERO: [u8; 16] = [0; BIT_ENCODING_SIZE];
120
121        let pos = dest.len();
122
123        // Write the zero encoding for the given range.
124        self.encode_range(direction, range, dest);
125        let dest = &mut dest[pos..];
126
127        for (pos, bit) in data.iter_lsb0().enumerate() {
128            // Add the delta to the encoding whenever the encoded bit is 1,
129            // otherwise add a zero.
130            let summand = if bit { &self.delta } else { &ZERO };
131            dest[pos * BIT_ENCODING_SIZE..(pos + 1) * BIT_ENCODING_SIZE]
132                .iter_mut()
133                .zip(summand)
134                .for_each(|(a, b)| *a ^= *b);
135        }
136    }
137}