1pub struct TranscriptBuilder {
27 domain: String,
28 data: Vec<(Vec<u8>, Vec<u8>)>,
29 prefix: Option<String>,
31}
32
33pub trait TranscriptBuilderInput {
55 const TYPE: &'static str;
57
58 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 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 pub fn with_prefix(mut self, prefix: String) -> Self {
88 self.prefix = Some(prefix);
89 self
90 }
91
92 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 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 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 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}