Skip to main content

ink_quill/
lib.rs

1// inspired by https://gist.github.com/qti3e/ed5b1e06514957f7032d3b41ff362c34
2
3/// The `TranscriptBuilder` struct is responsible for constructing a transcript of input data.
4/// It contains a `domain` for unique identification, a `data` vector to hold the input data,
5/// and an optional `prefix` for new labels.
6///
7/// # Examples
8///
9/// ```
10/// use ink_quill::TranscriptBuilder;
11/// let mut builder = TranscriptBuilder::empty("example domain");
12/// builder = builder.with("label", &123u64);
13/// let result = builder.compile();
14/// ```
15/// # Panics
16///
17/// Methods of this struct may panic if called with a duplicated label. To avoid this, ensure
18/// labels are unique and do not repeat.
19///
20/// # Notes
21///
22/// - The `domain` field is not just for descriptive purposes. It's used as a key in the Blake3
23///   hashing function, so changing it will result in different hashes for the same input data.
24/// - Methods of this struct may panic if called with a duplicated label. To avoid this, ensure
25///   labels are unique and do not repeat.
26pub struct TranscriptBuilder {
27    domain: String,
28    data: Vec<(Vec<u8>, Vec<u8>)>,
29    /// A prefix for all the new labels.
30    prefix: Option<String>,
31}
32
33/// `TranscriptBuilderInput` is a trait that provides an interface for any data
34/// that can be passed as input to the `TranscriptBuilder`. Implementers of this trait
35/// should provide a unique type identifier and a method to return serialized bytes for the data.
36///
37/// # Examples
38///
39/// ```
40/// use ink_quill::TranscriptBuilderInput;
41///
42/// struct MyData {
43///     value: u64,
44/// }
45///
46/// impl TranscriptBuilderInput for MyData {
47///     const TYPE: &'static str = "MyData";
48///
49///     fn to_transcript_builder_input(&self) -> Vec<u8> {
50///         self.value.to_be_bytes().to_vec()
51///     }
52/// }
53/// ```
54pub trait TranscriptBuilderInput {
55    /// A unique type identifier of this data type.
56    const TYPE: &'static str;
57
58    /// Return serialized bytes for the current data.
59    fn to_transcript_builder_input(&self) -> Vec<u8>;
60}
61
62impl TranscriptBuilder {
63    pub fn empty(domain: impl Into<String>) -> Self {
64        TranscriptBuilder {
65            domain: domain.into(),
66            data: Vec::new(),
67            prefix: None,
68        }
69    }
70
71    pub fn get_domain(&self) -> &str {
72        &self.domain
73    }
74
75    /// Merges another `TranscriptBuilder` instance into the current one. The function mutates the
76    /// current instance, extending its data with the data from the other instance. The function
77    /// consumes the other instance and returns the current instance.
78    pub fn merge(mut self, other: Self) -> Self {
79        for data in other.data {
80            self.append_data(data)
81        }
82        self
83    }
84
85    /// Set a prefix for the new values passed to the transcript builder, this can be used when you
86    /// want to use the transcript builder in a loop.
87    pub fn with_prefix(mut self, prefix: String) -> Self {
88        self.prefix = Some(prefix);
89        self
90    }
91
92    /// Provide the builder with the given input and label, the label MUST be unique. The ordering
93    /// of the data does not matter.
94    pub fn with<T: TranscriptBuilderInput>(mut self, label: &'static str, data: &T) -> Self {
95        let mut key = match &self.prefix {
96            Some(prefix) => format!("{prefix}/{label}").to_transcript_builder_input(),
97            None => label.to_transcript_builder_input(),
98        };
99        key.extend_from_slice(&(key.len() as u64).to_be_bytes());
100
101        let mut value = data.to_transcript_builder_input();
102        value.extend_from_slice(&(value.len() as u64).to_be_bytes());
103        value.extend_from_slice(b"/");
104        value.extend_from_slice(T::TYPE.as_bytes());
105        value.extend_from_slice(b"0");
106
107        let new_pair = (key, value);
108        self.append_data(new_pair);
109
110        self
111    }
112
113    /// Appends a tuple of vectors of u8 to the data of the TranscriptBuilder. The function sorts
114    /// the data vector to ensure that labels are unique.
115    ///
116    ///  # Panics
117    ///
118    /// If called with a duplicate label
119    fn append_data(&mut self, data: (Vec<u8>, Vec<u8>)) {
120        match self.data.binary_search_by(|(k, _)| k.cmp(&data.0)) {
121            Ok(_) => panic!("duplicate label"),
122            Err(idx) => self.data.insert(idx, data),
123        };
124    }
125
126    /// Returns a combined input stream from all the inputs this builder has already
127    /// been given.
128    pub fn compile(&self) -> Vec<u8> {
129        let mut result: Vec<u8> = Vec::new();
130        for (key, value) in &self.data {
131            result.extend(key);
132            result.extend(value);
133        }
134        result
135    }
136
137    /// Returns the hash of the value using the default blake3 hasher.
138    pub fn hash(&self) -> [u8; 32] {
139        fleek_blake3::derive_key(self.get_domain(), &self.compile())
140    }
141}
142
143impl TranscriptBuilderInput for Option<i32> {
144    const TYPE: &'static str = "i32";
145
146    fn to_transcript_builder_input(&self) -> Vec<u8> {
147        match self {
148            Some(v) => v.to_transcript_builder_input(),
149            None => vec![],
150        }
151    }
152}
153
154impl TranscriptBuilderInput for i32 {
155    const TYPE: &'static str = "i32";
156
157    fn to_transcript_builder_input(&self) -> Vec<u8> {
158        Vec::from(self.to_be_bytes())
159    }
160}
161
162impl TranscriptBuilderInput for Option<i64> {
163    const TYPE: &'static str = "i64";
164
165    fn to_transcript_builder_input(&self) -> Vec<u8> {
166        match self {
167            Some(v) => v.to_transcript_builder_input(),
168            None => vec![],
169        }
170    }
171}
172
173impl TranscriptBuilderInput for i64 {
174    const TYPE: &'static str = "i64";
175
176    fn to_transcript_builder_input(&self) -> Vec<u8> {
177        Vec::from(self.to_be_bytes())
178    }
179}
180
181impl TranscriptBuilderInput for u8 {
182    const TYPE: &'static str = "u8";
183
184    fn to_transcript_builder_input(&self) -> Vec<u8> {
185        Vec::from(self.to_be_bytes())
186    }
187}
188
189impl TranscriptBuilderInput for Option<u8> {
190    const TYPE: &'static str = "u8";
191
192    fn to_transcript_builder_input(&self) -> Vec<u8> {
193        match self {
194            Some(v) => v.to_transcript_builder_input(),
195            None => vec![],
196        }
197    }
198}
199
200impl TranscriptBuilderInput for u16 {
201    const TYPE: &'static str = "u16";
202
203    fn to_transcript_builder_input(&self) -> Vec<u8> {
204        Vec::from(self.to_be_bytes())
205    }
206}
207
208impl TranscriptBuilderInput for Option<u16> {
209    const TYPE: &'static str = "u16";
210
211    fn to_transcript_builder_input(&self) -> Vec<u8> {
212        match self {
213            Some(v) => v.to_transcript_builder_input(),
214            None => vec![],
215        }
216    }
217}
218
219impl TranscriptBuilderInput for Option<u32> {
220    const TYPE: &'static str = "u32";
221
222    fn to_transcript_builder_input(&self) -> Vec<u8> {
223        match self {
224            Some(v) => v.to_transcript_builder_input(),
225            None => vec![],
226        }
227    }
228}
229
230impl TranscriptBuilderInput for u32 {
231    const TYPE: &'static str = "u32";
232
233    fn to_transcript_builder_input(&self) -> Vec<u8> {
234        Vec::from(self.to_be_bytes())
235    }
236}
237
238impl TranscriptBuilderInput for u64 {
239    const TYPE: &'static str = "u64";
240
241    fn to_transcript_builder_input(&self) -> Vec<u8> {
242        Vec::from(self.to_be_bytes())
243    }
244}
245
246impl TranscriptBuilderInput for Option<u64> {
247    const TYPE: &'static str = "u64";
248
249    fn to_transcript_builder_input(&self) -> Vec<u8> {
250        match self {
251            Some(v) => v.to_transcript_builder_input(),
252            None => vec![],
253        }
254    }
255}
256
257impl TranscriptBuilderInput for u128 {
258    const TYPE: &'static str = "u128";
259
260    fn to_transcript_builder_input(&self) -> Vec<u8> {
261        Vec::from(self.to_be_bytes())
262    }
263}
264
265impl TranscriptBuilderInput for Option<u128> {
266    const TYPE: &'static str = "u128";
267
268    fn to_transcript_builder_input(&self) -> Vec<u8> {
269        match self {
270            Some(v) => v.to_transcript_builder_input(),
271            None => vec![],
272        }
273    }
274}
275
276impl TranscriptBuilderInput for String {
277    const TYPE: &'static str = "String";
278
279    fn to_transcript_builder_input(&self) -> Vec<u8> {
280        self.as_bytes().to_vec()
281    }
282}
283
284impl TranscriptBuilderInput for Option<String> {
285    const TYPE: &'static str = "String";
286
287    fn to_transcript_builder_input(&self) -> Vec<u8> {
288        match self {
289            Some(v) => v.as_bytes().to_vec(),
290            None => Vec::new(),
291        }
292    }
293}
294
295impl<'a> TranscriptBuilderInput for &'a str {
296    const TYPE: &'static str = "str";
297
298    fn to_transcript_builder_input(&self) -> Vec<u8> {
299        self.as_bytes().to_vec()
300    }
301}
302
303impl TranscriptBuilderInput for Option<Vec<u8>> {
304    const TYPE: &'static str = "buffer";
305
306    fn to_transcript_builder_input(&self) -> Vec<u8> {
307        match self {
308            Some(v) => v.clone(),
309            None => Vec::new(),
310        }
311    }
312}
313
314impl TranscriptBuilderInput for &[u8] {
315    const TYPE: &'static str = "buffer";
316
317    fn to_transcript_builder_input(&self) -> Vec<u8> {
318        self.to_vec()
319    }
320}
321
322impl TranscriptBuilderInput for Vec<u8> {
323    const TYPE: &'static str = "buffer";
324
325    fn to_transcript_builder_input(&self) -> Vec<u8> {
326        self.clone()
327    }
328}
329
330impl<const N: usize> TranscriptBuilderInput for [u8; N] {
331    const TYPE: &'static str = "buffer";
332
333    fn to_transcript_builder_input(&self) -> Vec<u8> {
334        Vec::from(self.as_ref())
335    }
336}
337
338#[cfg(test)]
339mod tests {
340    use fleek_blake3::derive_key;
341
342    use super::*;
343
344    #[test]
345    fn test_builder_compilation() {
346        let iq_1 = TranscriptBuilder::empty("test-digest")
347            .with("nonce", &202_u64)
348            .with("transaction", &"deposit")
349            .with_prefix("new".to_string())
350            .with("value", &1000_u64)
351            .with("type", &"FLK")
352            .compile();
353
354        let iq_2 = TranscriptBuilder::empty("test-digest")
355            .with("transaction", &"deposit")
356            .with("nonce", &202_u64)
357            .with_prefix("new".to_string())
358            .with("type", &"FLK")
359            .with("value", &1000_u64)
360            .compile();
361
362        assert_eq!(
363            derive_key("test-digest", &iq_1),
364            derive_key("test-digest", &iq_2)
365        );
366    }
367}