use std::fmt::{self, Debug, Display};
use std::str::FromStr;
use borsh::{BorshDeserialize, BorshSerialize};
use serde_with::{DeserializeFromStr, SerializeDisplay};
use sha2::{Digest, Sha256};
use crate::error::ParseHashError;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Default, SerializeDisplay, DeserializeFromStr)]
pub struct CryptoHash([u8; 32]);
impl CryptoHash {
pub const ZERO: Self = Self([0; 32]);
pub fn hash(data: &[u8]) -> Self {
let result = Sha256::digest(data);
let mut bytes = [0u8; 32];
bytes.copy_from_slice(&result);
Self(bytes)
}
pub const fn from_bytes(bytes: [u8; 32]) -> Self {
Self(bytes)
}
pub const fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
pub fn to_vec(&self) -> Vec<u8> {
self.0.to_vec()
}
pub fn is_zero(&self) -> bool {
self.0 == [0u8; 32]
}
}
impl FromStr for CryptoHash {
type Err = ParseHashError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = bs58::decode(s)
.into_vec()
.map_err(|e| ParseHashError::InvalidBase58(e.to_string()))?;
if bytes.len() != 32 {
return Err(ParseHashError::InvalidLength(bytes.len()));
}
let mut arr = [0u8; 32];
arr.copy_from_slice(&bytes);
Ok(Self(arr))
}
}
impl TryFrom<&str> for CryptoHash {
type Error = ParseHashError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
s.parse()
}
}
impl TryFrom<&[u8]> for CryptoHash {
type Error = ParseHashError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
if bytes.len() != 32 {
return Err(ParseHashError::InvalidLength(bytes.len()));
}
let mut arr = [0u8; 32];
arr.copy_from_slice(bytes);
Ok(Self(arr))
}
}
impl From<[u8; 32]> for CryptoHash {
fn from(bytes: [u8; 32]) -> Self {
Self(bytes)
}
}
impl AsRef<[u8]> for CryptoHash {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl Display for CryptoHash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", bs58::encode(&self.0).into_string())
}
}
impl Debug for CryptoHash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "CryptoHash({})", self)
}
}
impl BorshSerialize for CryptoHash {
fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
writer.write_all(&self.0)
}
}
impl BorshDeserialize for CryptoHash {
fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
let mut bytes = [0u8; 32];
reader.read_exact(&mut bytes)?;
Ok(Self(bytes))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hash() {
let hash = CryptoHash::hash(b"hello world");
assert!(!hash.is_zero());
assert_eq!(hash.as_bytes().len(), 32);
}
#[test]
fn test_display_parse_roundtrip() {
let hash = CryptoHash::hash(b"test data");
let s = hash.to_string();
let parsed: CryptoHash = s.parse().unwrap();
assert_eq!(hash, parsed);
}
#[test]
fn test_zero() {
assert!(CryptoHash::ZERO.is_zero());
assert!(!CryptoHash::hash(b"x").is_zero());
}
#[test]
fn test_from_bytes() {
let bytes = [42u8; 32];
let hash = CryptoHash::from_bytes(bytes);
assert_eq!(hash.as_bytes(), &bytes);
}
#[test]
fn test_to_vec() {
let hash = CryptoHash::hash(b"test");
let vec = hash.to_vec();
assert_eq!(vec.len(), 32);
assert_eq!(vec.as_slice(), hash.as_bytes());
}
#[test]
fn test_from_32_byte_array() {
let bytes = [1u8; 32];
let hash: CryptoHash = bytes.into();
assert_eq!(hash.as_bytes(), &bytes);
}
#[test]
fn test_try_from_slice_success() {
let bytes = [2u8; 32];
let hash = CryptoHash::try_from(bytes.as_slice()).unwrap();
assert_eq!(hash.as_bytes(), &bytes);
}
#[test]
fn test_try_from_slice_wrong_length() {
let bytes = [3u8; 16]; let result = CryptoHash::try_from(bytes.as_slice());
assert!(matches!(
result,
Err(crate::error::ParseHashError::InvalidLength(16))
));
}
#[test]
fn test_try_from_str() {
let hash = CryptoHash::hash(b"test");
let s = hash.to_string();
let parsed = CryptoHash::try_from(s.as_str()).unwrap();
assert_eq!(hash, parsed);
}
#[test]
fn test_as_ref() {
let hash = CryptoHash::hash(b"test");
let slice: &[u8] = hash.as_ref();
assert_eq!(slice.len(), 32);
assert_eq!(slice, hash.as_bytes());
}
#[test]
fn test_debug_format() {
let hash = CryptoHash::ZERO;
let debug = format!("{:?}", hash);
assert!(debug.starts_with("CryptoHash("));
assert!(debug.contains("1111111111")); }
#[test]
fn test_parse_invalid_base58() {
let result: Result<CryptoHash, _> = "invalid!@#$%base58".parse();
assert!(matches!(
result,
Err(crate::error::ParseHashError::InvalidBase58(_))
));
}
#[test]
fn test_parse_wrong_length() {
let result: Result<CryptoHash, _> = "3xRDxw".parse();
assert!(matches!(
result,
Err(crate::error::ParseHashError::InvalidLength(_))
));
}
#[test]
fn test_serde_roundtrip() {
let hash = CryptoHash::hash(b"serde test");
let json = serde_json::to_string(&hash).unwrap();
let parsed: CryptoHash = serde_json::from_str(&json).unwrap();
assert_eq!(hash, parsed);
}
#[test]
fn test_borsh_roundtrip() {
let hash = CryptoHash::hash(b"borsh test");
let bytes = borsh::to_vec(&hash).unwrap();
assert_eq!(bytes.len(), 32);
let parsed: CryptoHash = borsh::from_slice(&bytes).unwrap();
assert_eq!(hash, parsed);
}
#[test]
fn test_hash_deterministic() {
let hash1 = CryptoHash::hash(b"same input");
let hash2 = CryptoHash::hash(b"same input");
assert_eq!(hash1, hash2);
let hash3 = CryptoHash::hash(b"different input");
assert_ne!(hash1, hash3);
}
#[test]
fn test_default() {
let hash = CryptoHash::default();
assert!(hash.is_zero());
assert_eq!(hash, CryptoHash::ZERO);
}
#[test]
fn test_clone() {
let hash1 = CryptoHash::hash(b"clone test");
#[allow(clippy::clone_on_copy)]
let hash2 = hash1.clone(); assert_eq!(hash1, hash2);
}
#[test]
fn test_hash_comparison() {
let hash1 = CryptoHash::from_bytes([0u8; 32]);
let hash2 = CryptoHash::from_bytes([1u8; 32]);
assert_ne!(hash1, hash2);
assert_eq!(hash1, CryptoHash::ZERO);
}
}