use core::{fmt, str::FromStr};
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Visitor};
use sha2::{Digest, Sha256};
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Sha256Hash([u8; 32]);
impl Sha256Hash {
#[must_use]
pub fn compute(bytes: &[u8]) -> Self {
Self(Sha256::digest(bytes).into())
}
pub fn parse(value: &str) -> Result<Self, ParseSha256Error> {
value.parse()
}
#[must_use]
pub const fn from_bytes(bytes: [u8; 32]) -> Self {
Self(bytes)
}
#[must_use]
pub const fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
}
impl FromStr for Sha256Hash {
type Err = ParseSha256Error;
fn from_str(value: &str) -> Result<Self, Self::Err> {
if value.len() != 64 {
return Err(ParseSha256Error);
}
let mut bytes = [0_u8; 32];
for (index, pair) in value.as_bytes().chunks_exact(2).enumerate() {
bytes[index] = (nibble(pair[0])? << 4) | nibble(pair[1])?;
}
Ok(Self(bytes))
}
}
impl fmt::Display for Sha256Hash {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.0 {
write!(formatter, "{byte:02x}")?;
}
Ok(())
}
}
impl fmt::Debug for Sha256Hash {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "Sha256Hash(\"{self}\")")
}
}
impl Serialize for Sha256Hash {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(self)
}
}
impl<'de> Deserialize<'de> for Sha256Hash {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(Sha256Visitor)
}
}
struct Sha256Visitor;
impl Visitor<'_> for Sha256Visitor {
type Value = Sha256Hash;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("64 lowercase hexadecimal SHA-256 characters")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Sha256Hash::parse(value).map_err(E::custom)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ParseSha256Error;
impl fmt::Display for ParseSha256Error {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("SHA-256 must contain 64 lowercase hexadecimal characters")
}
}
impl std::error::Error for ParseSha256Error {}
fn nibble(byte: u8) -> Result<u8, ParseSha256Error> {
match byte {
b'0'..=b'9' => Ok(byte - b'0'),
b'a'..=b'f' => Ok(byte - b'a' + 10),
_ => Err(ParseSha256Error),
}
}
pub(crate) struct Sha256Hasher(Sha256);
impl Sha256Hasher {
pub(crate) fn new() -> Self {
Self(Sha256::new())
}
pub(crate) fn update(&mut self, bytes: &[u8]) {
self.0.update(bytes);
}
pub(crate) fn finish(self) -> Sha256Hash {
Sha256Hash::from_bytes(self.0.finalize().into())
}
}
impl std::io::Write for Sha256Hasher {
fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
self.update(bytes);
Ok(bytes.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
pub(crate) fn serialized_hash<T>(value: &T) -> Result<Sha256Hash, blazingly_json::Error>
where
T: Serialize + ?Sized,
{
let mut hasher = Sha256Hasher::new();
blazingly_json::to_writer(&mut hasher, value)?;
Ok(hasher.finish())
}
#[cfg(test)]
mod tests {
use crate::hash::{Sha256Hash, Sha256Hasher, serialized_hash};
#[test]
fn matches_standard_vectors_and_incremental_updates() {
let expected = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad";
assert_eq!(Sha256Hash::compute(b"abc").to_string(), expected);
let mut hasher = Sha256Hasher::new();
hasher.update(b"a");
hasher.update(b"bc");
assert_eq!(hasher.finish().to_string(), expected);
}
#[test]
fn parsing_is_exact_and_lowercase() {
let digest = Sha256Hash::compute(b"");
assert_eq!(digest.to_string().parse(), Ok(digest));
assert!(
digest
.to_string()
.to_uppercase()
.parse::<Sha256Hash>()
.is_err()
);
assert!("00".parse::<Sha256Hash>().is_err());
}
#[test]
fn streaming_json_hash_matches_the_previous_buffered_contract() {
fn assert_same<T: serde::Serialize>(value: &T) {
let buffered = blazingly_json::to_vec(value).unwrap();
assert_eq!(
serialized_hash(value).unwrap(),
Sha256Hash::compute(&buffered)
);
}
assert_same(&weavatrix_refactor_plan::EditPlan::new(
"legacy",
Vec::new(),
));
}
}