Skip to content

Commit

Permalink
feat: add newtype wrapper for Hash that is compatible with Cid
Browse files Browse the repository at this point in the history
  • Loading branch information
rklaehn committed Feb 7, 2023
1 parent 3eff210 commit 9db0937
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 0 deletions.
39 changes: 39 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ blake3 = "1.3.3"
bytes = "1"
clap = { version = "4", features = ["derive"], optional = true }
console = { version = "0.15.5", optional = true }
data-encoding = "2.3.3"
der = { version = "0.6", features = ["alloc", "derive"] }
ed25519-dalek = { version = "1.0.1", features = ["serde"] }
futures = "0.3.25"
indicatif = { version = "0.17", features = ["tokio"], optional = true }
multibase = "0.9.1"
postcard = { version = "1", default-features = false, features = ["alloc", "use-std", "experimental-derive"] }
quinn = "0.9.3"
rand = "0.7"
Expand Down
100 changes: 100 additions & 0 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,86 @@ impl MaxSize for Hash {
const POSTCARD_MAX_SIZE: usize = 32;
}

#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Blake3Cid(Hash);

const CID_PREFIX: [u8; 4] = [0x01, 0x55, 0x1e, 0x20];

impl Blake3Cid {
pub fn new(hash: Hash) -> Self {
Blake3Cid(hash)
}

pub fn hash(&self) -> &Hash {
&self.0
}

pub fn into_bytes(&self) -> [u8; 36] {
let hash: [u8; 32] = self.0 .0.into();
let mut res = [0u8; 36];
res[0..4].copy_from_slice(&CID_PREFIX);
res[4..36].copy_from_slice(&hash);
res
}

pub fn from_bytes(bytes: &[u8]) -> anyhow::Result<Self> {
ensure!(
bytes.len() == 36,
"invalid cid length, expected 36, got {}",
bytes.len()
);
ensure!(bytes[0..4] == CID_PREFIX, "invalid cid prefix");
let mut hash = [0u8; 32];
hash.copy_from_slice(&bytes[4..36]);
Ok(Blake3Cid(Hash::from(hash)))
}
}

impl Display for Blake3Cid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// result will be 58 bytes plus prefix
let mut res = [b'b'; 59];
// write the encoded bytes
data_encoding::BASE32_NOPAD.encode_mut(&self.into_bytes(), &mut res[1..]);
// convert to string, this is guaranteed to succeed
let t = std::str::from_utf8_mut(res.as_mut()).unwrap();
// hack since data_encoding doesn't have BASE32LOWER_NOPAD as a const
t.make_ascii_lowercase();
// write the str, no allocations
f.write_str(t)
}
}

impl FromStr for Blake3Cid {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let sb = s.as_bytes();
if sb.len() == 59 && sb[0] == b'b' {
// this is a base32 encoded cid, we can decode it directly
let mut t = [0u8; 58];
t.copy_from_slice(&sb[1..]);
// hack since data_encoding doesn't have BASE32LOWER_NOPAD as a const
std::str::from_utf8_mut(t.as_mut())
.unwrap()
.make_ascii_uppercase();
// decode the bytes
let mut res = [0u8; 36];
data_encoding::BASE32_NOPAD
.decode_mut(&t, &mut res)
.map_err(|_e| anyhow::anyhow!("invalid base32"))?;
// convert to cid, this will check the prefix
Self::from_bytes(&res)
} else {
// if we want to support all the weird multibase prefixes, we have no choice
// but to use the multibase crate
let (_base, bytes) = multibase::decode(s)?;
Self::from_bytes(bytes.as_ref())
}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -130,4 +210,24 @@ mod tests {
let encoded = hash.to_string();
assert_eq!(encoded.parse::<Hash>().unwrap(), hash);
}

#[test]
fn test_cid() {
let expected = "bafkr4igxjga67jykbseaxdmmdgc5a5o3zp3htom2l6mrjznk7fvyggu6eq";
let data = b"hello world";
let hash = Hash::new(data);
let cid = Blake3Cid::new(hash);
// test to_string and parse from base32lower
assert_eq!(cid.to_string(), expected.to_string());
assert_eq!(Blake3Cid::from_str(expected).unwrap(), cid);
// test parse from other multibase encodings
for encoding in [
multibase::Base::Base58Btc,
multibase::Base::Base64,
multibase::Base::Base32Upper,
] {
let encoded = multibase::encode(encoding, cid.into_bytes());
assert_eq!(Blake3Cid::from_str(&encoded).unwrap(), cid);
}
}
}

0 comments on commit 9db0937

Please sign in to comment.