diff --git a/Cargo.lock b/Cargo.lock index 3845595..bbc8e57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,6 +130,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "generic-array" version = "0.14.7" @@ -170,6 +176,15 @@ version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +[[package]] +name = "mast" +version = "0.1.0" +dependencies = [ + "blake3", + "bytes", + "varu64", +] + [[package]] name = "password-hash" version = "0.5.0" @@ -235,12 +250,44 @@ dependencies = [ "getrandom", ] +[[package]] +name = "snafu" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7" +dependencies = [ + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "subtle" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.39" @@ -269,7 +316,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.39", ] [[package]] @@ -284,6 +331,15 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "varu64" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0b4bcb210ab3a048eda9a938508b072e474af2838994e77976c817e51af1e3" +dependencies = [ + "snafu", +] + [[package]] name = "version_check" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index ecba9db..e523c39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "kytz", + "mast", ] # See: https://github.com/rust-lang/rust/issues/90148#issuecomment-949194352 diff --git a/mast/Cargo.toml b/mast/Cargo.toml new file mode 100644 index 0000000..1ab6f87 --- /dev/null +++ b/mast/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "mast" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +blake3 = "1.5.0" +bytes = "1.5.0" +varu64 = "0.7.0" diff --git a/mast/src/lib.rs b/mast/src/lib.rs new file mode 100644 index 0000000..efcb3f0 --- /dev/null +++ b/mast/src/lib.rs @@ -0,0 +1,18 @@ +#![allow(unused)] + +mod node; +mod storage; +mod tree; + +use crate::node::Node; + +// // TODO: maybe add nonce for encrypted trees. +// // TODO: why add header in each node? or in the Mast commit? +// // Would we need to read a node without traversing down from the Mast commit? +// pub struct Mast { +// /// The name of this Mast to be used as a prefix for all nodes +// /// in the storage, seperating different Masts. +// name: String, +// root: Option, +// storage: MastStorage, +// } diff --git a/mast/src/main.rs b/mast/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/mast/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/mast/src/node.rs b/mast/src/node.rs new file mode 100644 index 0000000..aa3172d --- /dev/null +++ b/mast/src/node.rs @@ -0,0 +1,129 @@ +//! Zip tree node. + +use blake3::{Hash, Hasher}; +use bytes::{BufMut, Bytes, BytesMut}; + +pub const HASH_LEN: usize = blake3::OUT_LEN; +pub const EMPTY_HASH: Hash = Hash::from_bytes([0_u8; HASH_LEN]); + +#[derive(Debug)] +/// A serialized node. +pub struct Node { + key: String, + value: Hash, + left: Option, + right: Option, +} + +impl Node { + pub fn new(key: String, value: Hash) -> Self { + Self { + key, + value, + left: None, + right: None, + } + } + + // === Getters === + + // pub fn key(&self) -> &String { + // &self.key + // } + + // pub fn left(&self) -> Option { + // self.left + // } + + // === Public Methods === + // + pub fn serialize(&self) -> Bytes { + let mut bytes = BytesMut::new(); + + let mut header = 0_u8; + if self.left.is_some() { + header |= 0b0000_0010; + } + if self.right.is_some() { + header |= 0b0000_0001; + } + bytes.put_u8(0_u8); + + // Encode the key + let mut key_len = [0_u8; 9]; + let size = varu64::encode(self.key.len() as u64, &mut key_len); + bytes.extend_from_slice(&key_len[..size]); + bytes.extend_from_slice(self.key.as_bytes()); + + // Encode the value + bytes.extend_from_slice(self.value.as_bytes()); + + if let Some(left) = self.left { + bytes.extend_from_slice(left.as_bytes()); + } + if let Some(right) = self.right { + bytes.extend_from_slice(right.as_bytes()); + } + + bytes.freeze() + } + + // pub fn hash(&self) -> Hash { + // let mut hasher = Hasher::new(); + // hasher.update(&self.serialize()); + // hasher.finalize().into() + // } + + pub fn deserialize(encoded: &Bytes) -> Result { + let header = encoded.first().ok_or(())?; + + let (n, rest) = varu64::decode(&encoded[1..]).map_err(|_| ())?; + let key_len = n as usize; + let key = String::from_utf8(rest[..key_len].to_vec()).map_err(|_| ())?; + let value = Hash::from_bytes( + rest[key_len..key_len + HASH_LEN] + .try_into() + .map_err(|_| ())?, + ); + + let mut left: Option = None; + let mut right: Option = None; + + if *header & 0b0000_0010 != 0 { + let start = key_len + HASH_LEN; + let end = start + HASH_LEN; + + left = Some(Hash::from_bytes( + rest[start..end].try_into().map_err(|_| ())?, + )) + } + if *header & 0b0000_0001 != 0 { + let start = key_len + HASH_LEN + if left.is_some() { HASH_LEN } else { 0 }; + let end = start + HASH_LEN; + + right = Some(Hash::from_bytes( + rest[start..end].try_into().map_err(|_| ())?, + )) + } + + Ok(Self { + key, + value, + left, + right, + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn encode() { + let node = Node::new("key".to_string(), EMPTY_HASH); + + let encoded = node.serialize(); + let decoded = Node::deserialize(&encoded); + } +} diff --git a/mast/src/storage/memory.rs b/mast/src/storage/memory.rs new file mode 100644 index 0000000..12b1fbf --- /dev/null +++ b/mast/src/storage/memory.rs @@ -0,0 +1,36 @@ +//! In memory Mast storage. + +use blake3::{Hash, Hasher}; +use bytes::{Bytes, BytesMut}; +use std::collections::HashMap; + +// TODO: storage abstraction. +#[derive(Debug)] +pub struct Storage { + storage: HashMap, +} + +impl Storage { + pub fn new() -> Self { + Self { + storage: HashMap::default(), + } + } + + pub fn get(&self, hash: &Hash) -> Option { + self.storage.get(hash).cloned() + } + + pub fn insert_bytes(&mut self, bytes: Bytes) -> Hash { + let hash = Hasher::new().update(&bytes).finalize(); + // TODO: should I add a prefix here? + self.storage.insert(hash, bytes); + hash + } +} + +impl Default for Storage { + fn default() -> Self { + Self::new() + } +} diff --git a/mast/src/storage/mod.rs b/mast/src/storage/mod.rs new file mode 100644 index 0000000..eb29191 --- /dev/null +++ b/mast/src/storage/mod.rs @@ -0,0 +1 @@ +pub mod memory; diff --git a/mast/src/tree.rs b/mast/src/tree.rs new file mode 100644 index 0000000..c5dc023 --- /dev/null +++ b/mast/src/tree.rs @@ -0,0 +1,100 @@ +//! Zip tree implementation. + +use blake3::{Hash, Hasher}; +use bytes::{Bytes, BytesMut}; +use std::collections::btree_map::BTreeMap; +use std::fmt::{self, Debug, Display, Formatter}; + +use crate::node::Node; +use crate::storage; + +#[derive(Debug)] +pub struct ZipTree { + root: Option, + storage: storage::memory::Storage, +} + +impl ZipTree { + pub fn new() -> Self { + Self { + root: None, + storage: storage::memory::Storage::default(), + } + } + + pub fn insert(&mut self, key: &[u8], value: &[u8]) -> &mut Self { + // let node = Node::new(key, value); + // + // let hash = node.hash(); + // + // self.storage.insert(hash, node.serialize().into()); + // self.root = Some(hash); + + self + } + + // pub fn node(&self, hash: &Hash) -> Option { + // if let Some(encoded_node) = self.storage.get(hash) { + // return Node::deserialize(encoded_node).ok().or(None); + // }; + // + // None + // } +} + +// impl Display for ZipTree { +// fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { +// writeln!(f, "graph TD;"); +// +// let mut stack: Vec = Vec::new(); +// +// if let Some(root_id) = self.root { +// if let Some(node) = self.node(&root_id) { +// stack.push(node); +// } +// } +// +// while let Some(node) = stack.pop() { +// let left = self.node(&node.left()); +// let right = self.node(&node.right()); +// +// match (&left, &right) { +// (None, None) => { +// writeln!(f, " {:?}", node.key()); +// } +// _ => { +// if let Some(left) = left { +// writeln!(f, " {:?} --> {:?}", node.key(), left.key()); +// } +// if let Some(right) = right { +// writeln!(f, " {:?} --> {:?}", node.key(), right.key()); +// } +// } +// } +// } +// +// Ok(()) +// } +// } + +// impl Debug for ZipTree { +// fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { +// write!(f, "{}", self); +// +// Ok(()) +// } +// } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_new() { + let mut tree = ZipTree::new(); + + tree.insert(b"foo", b"bar"); + + dbg!(tree); + } +}