upend/base/src/hash.rs

182 lines
4.8 KiB
Rust

use std::fmt;
use crate::{addressing::Address, error::UpEndError};
use multihash::Hasher;
use serde::{
de::{self, Visitor},
ser, Deserialize, Deserializer, Serialize, Serializer,
};
/// multihash SHA2-256 code
pub const SHA2_256: u64 = 0x12;
/// multihash identity code
pub const IDENTITY: u64 = 0x00;
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "diesel", derive(diesel::FromSqlRow))]
pub struct UpMultihash(LargeMultihash);
impl UpMultihash {
pub fn to_bytes(&self) -> Vec<u8> {
self.0.to_bytes()
}
pub fn from_bytes<T: AsRef<[u8]>>(input: T) -> Result<Self, UpEndError> {
Ok(UpMultihash(
LargeMultihash::from_bytes(input.as_ref())
.map_err(|e| UpEndError::HashDecodeError(e.to_string()))?,
))
}
pub fn from_sha256<T: AsRef<[u8]>>(input: T) -> Result<Self, UpEndError> {
Ok(UpMultihash(
LargeMultihash::wrap(SHA2_256, input.as_ref()).map_err(UpEndError::from_any)?,
))
}
}
impl std::fmt::Display for UpMultihash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", b58_encode(self.to_bytes()))
}
}
pub(crate) type LargeMultihash = multihash::MultihashGeneric<256>;
impl From<LargeMultihash> for UpMultihash {
fn from(value: LargeMultihash) -> Self {
UpMultihash(value)
}
}
impl From<&UpMultihash> for LargeMultihash {
fn from(value: &UpMultihash) -> Self {
value.0
}
}
impl Serialize for UpMultihash {
fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
where
S: Serializer,
{
serializer.serialize_str(
b58_encode(
Address::Hash(self.clone())
.encode()
.map_err(ser::Error::custom)?,
)
.as_str(),
)
}
}
struct UpMultihashVisitor;
impl<'de> Visitor<'de> for UpMultihashVisitor {
type Value = UpMultihash;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a valid UpEnd address (hash/UUID) as a multi-hashed string")
}
fn visit_str<E>(self, str: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
let bytes = b58_decode(str)
.map_err(|e| de::Error::custom(format!("Error deserializing UpMultihash: {}", e)))?;
Ok(UpMultihash(LargeMultihash::from_bytes(&bytes).map_err(
|e| de::Error::custom(format!("Error parsing UpMultihash: {}", e)),
)?))
}
}
impl<'de> Deserialize<'de> for UpMultihash {
fn deserialize<D>(deserializer: D) -> Result<UpMultihash, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(UpMultihashVisitor)
}
}
#[cfg(feature = "diesel")]
impl diesel::types::FromSql<diesel::sql_types::Binary, diesel::sqlite::Sqlite> for UpMultihash {
fn from_sql(
bytes: Option<&<diesel::sqlite::Sqlite as diesel::backend::Backend>::RawValue>,
) -> diesel::deserialize::Result<Self> {
Ok(UpMultihash(LargeMultihash::from_bytes(
diesel::not_none!(bytes).read_blob(),
)?))
}
}
pub fn sha256hash<T: AsRef<[u8]>>(input: T) -> Result<UpMultihash, AsMultihashError> {
let mut hasher = multihash::Sha2_256::default();
hasher.update(input.as_ref());
Ok(UpMultihash(
LargeMultihash::wrap(SHA2_256, hasher.finalize())
.map_err(|e| AsMultihashError(e.to_string()))?,
))
}
pub fn b58_encode<T: AsRef<[u8]>>(vec: T) -> String {
multibase::encode(multibase::Base::Base58Btc, vec.as_ref())
}
pub fn b58_decode<T: AsRef<str>>(input: T) -> Result<Vec<u8>, UpEndError> {
let input = input.as_ref();
let (_base, data) =
multibase::decode(input).map_err(|err| UpEndError::HashDecodeError(err.to_string()))?;
Ok(data)
}
#[derive(Debug, Clone)]
pub struct AsMultihashError(pub String);
impl std::fmt::Display for AsMultihashError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::error::Error for AsMultihashError {}
impl From<std::io::Error> for AsMultihashError {
fn from(err: std::io::Error) -> Self {
AsMultihashError(err.to_string())
}
}
pub trait AsMultihash {
fn as_multihash(&self) -> Result<UpMultihash, AsMultihashError>;
}
impl<T> AsMultihash for T
where
T: AsRef<[u8]>,
{
fn as_multihash(&self) -> Result<UpMultihash, AsMultihashError> {
sha256hash(self)
}
}
#[cfg(test)]
mod tests {
use crate::hash::{b58_decode, b58_encode};
#[test]
fn test_encode_decode() {
let content = "Hello, World!".as_bytes();
let encoded = b58_encode(content);
let decoded = b58_decode(encoded);
assert!(decoded.is_ok());
assert_eq!(content, decoded.unwrap());
}
}