pub struct TranscriptBuilder {
domain: String,
data: Vec<(Vec<u8>, Vec<u8>)>,
prefix: Option<String>,
}
pub trait TranscriptBuilderInput {
const TYPE: &'static str;
fn to_transcript_builder_input(&self) -> Vec<u8>;
}
impl TranscriptBuilder {
pub fn empty(domain: impl Into<String>) -> Self {
TranscriptBuilder {
domain: domain.into(),
data: Vec::new(),
prefix: None,
}
}
pub fn get_domain(&self) -> &str {
&self.domain
}
pub fn merge(mut self, other: Self) -> Self {
for data in other.data {
self.append_data(data)
}
self
}
pub fn with_prefix(mut self, prefix: String) -> Self {
self.prefix = Some(prefix);
self
}
pub fn with<T: TranscriptBuilderInput>(mut self, label: &'static str, data: &T) -> Self {
let mut key = match &self.prefix {
Some(prefix) => format!("{prefix}/{label}").to_transcript_builder_input(),
None => label.to_transcript_builder_input(),
};
key.extend_from_slice(&(key.len() as u64).to_be_bytes());
let mut value = data.to_transcript_builder_input();
value.extend_from_slice(&(value.len() as u64).to_be_bytes());
value.extend_from_slice(b"/");
value.extend_from_slice(T::TYPE.as_bytes());
value.extend_from_slice(b"0");
let new_pair = (key, value);
self.append_data(new_pair);
self
}
fn append_data(&mut self, data: (Vec<u8>, Vec<u8>)) {
match self.data.binary_search_by(|(k, _)| k.cmp(&data.0)) {
Ok(_) => panic!("duplicate label"),
Err(idx) => self.data.insert(idx, data),
};
}
pub fn compile(&self) -> Vec<u8> {
let mut result: Vec<u8> = Vec::new();
for (key, value) in &self.data {
result.extend(key);
result.extend(value);
}
result
}
pub fn hash(&self) -> [u8; 32] {
fleek_blake3::derive_key(self.get_domain(), &self.compile())
}
}
impl TranscriptBuilderInput for Option<i32> {
const TYPE: &'static str = "i32";
fn to_transcript_builder_input(&self) -> Vec<u8> {
match self {
Some(v) => v.to_transcript_builder_input(),
None => vec![],
}
}
}
impl TranscriptBuilderInput for i32 {
const TYPE: &'static str = "i32";
fn to_transcript_builder_input(&self) -> Vec<u8> {
Vec::from(self.to_be_bytes())
}
}
impl TranscriptBuilderInput for Option<i64> {
const TYPE: &'static str = "i64";
fn to_transcript_builder_input(&self) -> Vec<u8> {
match self {
Some(v) => v.to_transcript_builder_input(),
None => vec![],
}
}
}
impl TranscriptBuilderInput for i64 {
const TYPE: &'static str = "i64";
fn to_transcript_builder_input(&self) -> Vec<u8> {
Vec::from(self.to_be_bytes())
}
}
impl TranscriptBuilderInput for u8 {
const TYPE: &'static str = "u8";
fn to_transcript_builder_input(&self) -> Vec<u8> {
Vec::from(self.to_be_bytes())
}
}
impl TranscriptBuilderInput for Option<u8> {
const TYPE: &'static str = "u8";
fn to_transcript_builder_input(&self) -> Vec<u8> {
match self {
Some(v) => v.to_transcript_builder_input(),
None => vec![],
}
}
}
impl TranscriptBuilderInput for u16 {
const TYPE: &'static str = "u16";
fn to_transcript_builder_input(&self) -> Vec<u8> {
Vec::from(self.to_be_bytes())
}
}
impl TranscriptBuilderInput for Option<u16> {
const TYPE: &'static str = "u16";
fn to_transcript_builder_input(&self) -> Vec<u8> {
match self {
Some(v) => v.to_transcript_builder_input(),
None => vec![],
}
}
}
impl TranscriptBuilderInput for Option<u32> {
const TYPE: &'static str = "u32";
fn to_transcript_builder_input(&self) -> Vec<u8> {
match self {
Some(v) => v.to_transcript_builder_input(),
None => vec![],
}
}
}
impl TranscriptBuilderInput for u32 {
const TYPE: &'static str = "u32";
fn to_transcript_builder_input(&self) -> Vec<u8> {
Vec::from(self.to_be_bytes())
}
}
impl TranscriptBuilderInput for u64 {
const TYPE: &'static str = "u64";
fn to_transcript_builder_input(&self) -> Vec<u8> {
Vec::from(self.to_be_bytes())
}
}
impl TranscriptBuilderInput for Option<u64> {
const TYPE: &'static str = "u64";
fn to_transcript_builder_input(&self) -> Vec<u8> {
match self {
Some(v) => v.to_transcript_builder_input(),
None => vec![],
}
}
}
impl TranscriptBuilderInput for u128 {
const TYPE: &'static str = "u128";
fn to_transcript_builder_input(&self) -> Vec<u8> {
Vec::from(self.to_be_bytes())
}
}
impl TranscriptBuilderInput for Option<u128> {
const TYPE: &'static str = "u128";
fn to_transcript_builder_input(&self) -> Vec<u8> {
match self {
Some(v) => v.to_transcript_builder_input(),
None => vec![],
}
}
}
impl TranscriptBuilderInput for String {
const TYPE: &'static str = "String";
fn to_transcript_builder_input(&self) -> Vec<u8> {
self.as_bytes().to_vec()
}
}
impl TranscriptBuilderInput for Option<String> {
const TYPE: &'static str = "String";
fn to_transcript_builder_input(&self) -> Vec<u8> {
match self {
Some(v) => v.as_bytes().to_vec(),
None => Vec::new(),
}
}
}
impl<'a> TranscriptBuilderInput for &'a str {
const TYPE: &'static str = "str";
fn to_transcript_builder_input(&self) -> Vec<u8> {
self.as_bytes().to_vec()
}
}
impl TranscriptBuilderInput for Option<Vec<u8>> {
const TYPE: &'static str = "buffer";
fn to_transcript_builder_input(&self) -> Vec<u8> {
match self {
Some(v) => v.clone(),
None => Vec::new(),
}
}
}
impl TranscriptBuilderInput for &[u8] {
const TYPE: &'static str = "buffer";
fn to_transcript_builder_input(&self) -> Vec<u8> {
self.to_vec()
}
}
impl TranscriptBuilderInput for Vec<u8> {
const TYPE: &'static str = "buffer";
fn to_transcript_builder_input(&self) -> Vec<u8> {
self.clone()
}
}
impl<const N: usize> TranscriptBuilderInput for [u8; N] {
const TYPE: &'static str = "buffer";
fn to_transcript_builder_input(&self) -> Vec<u8> {
Vec::from(self.as_ref())
}
}
#[cfg(test)]
mod tests {
use fleek_blake3::derive_key;
use super::*;
#[test]
fn test_builder_compilation() {
let iq_1 = TranscriptBuilder::empty("test-digest")
.with("nonce", &202_u64)
.with("transaction", &"deposit")
.with_prefix("new".to_string())
.with("value", &1000_u64)
.with("type", &"FLK")
.compile();
let iq_2 = TranscriptBuilder::empty("test-digest")
.with("transaction", &"deposit")
.with("nonce", &202_u64)
.with_prefix("new".to_string())
.with("type", &"FLK")
.with("value", &1000_u64)
.compile();
assert_eq!(
derive_key("test-digest", &iq_1),
derive_key("test-digest", &iq_2)
);
}
}