use core::fmt;
use serde::{Deserialize, Serialize};
use crate::error::IdError;
pub use multihash::Error as MultihashError;
pub const HASH_SHA2_256: u64 = 0x12;
pub const HASH_BLAKE3_256: u64 = 0x1e;
#[derive(Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Multihash(multihash::Multihash<64>);
impl Multihash {
#[must_use]
pub fn sha2_256(bytes: &[u8]) -> Self {
use multihash_codetable::{Code, MultihashDigest};
Self(Code::Sha2_256.digest(bytes))
}
#[must_use]
pub fn blake3_256(bytes: &[u8]) -> Self {
use multihash_codetable::{Code, MultihashDigest};
Self(Code::Blake3_256.digest(bytes))
}
pub fn wrap(code: u64, digest: &[u8]) -> Result<Self, IdError> {
multihash::Multihash::<64>::wrap(code, digest)
.map(Self)
.map_err(|source| IdError::Multihash { source })
}
#[must_use]
pub const fn code(&self) -> u64 {
self.0.code()
}
#[must_use]
pub const fn size(&self) -> u8 {
self.0.size()
}
#[must_use]
pub fn digest(&self) -> &[u8] {
self.0.digest()
}
#[must_use]
pub fn to_bytes(&self) -> Vec<u8> {
self.0.to_bytes()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, IdError> {
multihash::Multihash::<64>::from_bytes(bytes)
.map(Self)
.map_err(|source| IdError::Multihash { source })
}
#[must_use]
pub(crate) const fn into_inner(self) -> multihash::Multihash<64> {
self.0
}
}
impl fmt::Debug for Multihash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"multihash(code=0x{:x}, size={}, digest=",
self.code(),
self.size()
)?;
for b in self.digest().iter().take(4) {
write!(f, "{b:02x}")?;
}
f.write_str("…)")
}
}
impl From<multihash::Multihash<64>> for Multihash {
fn from(m: multihash::Multihash<64>) -> Self {
Self(m)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sha2_256_deterministic() {
let a = Multihash::sha2_256(b"hello");
let b = Multihash::sha2_256(b"hello");
assert_eq!(a, b);
assert_eq!(a.code(), HASH_SHA2_256);
assert_eq!(a.size(), 32);
assert_eq!(a.digest().len(), 32);
}
#[test]
fn different_inputs_different_hashes() {
let a = Multihash::sha2_256(b"hello");
let b = Multihash::sha2_256(b"world");
assert_ne!(a, b);
}
#[test]
fn different_algos_distinct_even_on_empty() {
let sha = Multihash::sha2_256(&[]);
let blake = Multihash::blake3_256(&[]);
assert_ne!(
sha, blake,
"sha2-256 and blake3 of empty must not compare equal"
);
assert_ne!(sha.code(), blake.code());
}
#[test]
fn wire_round_trip() {
let original = Multihash::sha2_256(b"round-trip me");
let bytes = original.to_bytes();
let decoded = Multihash::from_bytes(&bytes).expect("decode");
assert_eq!(original, decoded);
assert_eq!(bytes[0], 0x12);
assert_eq!(bytes[1], 0x20);
assert_eq!(bytes.len(), 34);
}
#[test]
fn wrap_roundtrip() {
let digest = [0xabu8; 32];
let m = Multihash::wrap(HASH_SHA2_256, &digest).expect("wrap");
assert_eq!(m.code(), HASH_SHA2_256);
assert_eq!(m.digest(), &digest);
}
}