use crate::base::canonical::canonical_json;
use serde::Serialize;
use sha2::{Digest, Sha256};
pub type Hash256 = [u8; 32];
pub fn sha256(data: &[u8]) -> Hash256 {
let mut hasher = Sha256::new();
hasher.update(data);
hasher.finalize().into()
}
pub fn sha256_canonical<T: Serialize>(value: &T) -> Result<Hash256, serde_json::Error> {
let bytes = canonical_json(value)?;
Ok(sha256(&bytes))
}
pub fn sha256_utf8(s: &str) -> Hash256 {
sha256(s.as_bytes())
}
pub fn hex_encode(hash: &Hash256) -> String {
hash.iter().map(|b| format!("{:02x}", b)).collect()
}
pub fn hex_decode(s: &str) -> Option<Hash256> {
let bytes = s.as_bytes();
if bytes.len() != 64 {
return None;
}
fn nibble(b: u8) -> Option<u8> {
match b {
b'0'..=b'9' => Some(b - b'0'),
b'a'..=b'f' => Some(b - b'a' + 10),
_ => None,
}
}
let mut result = [0u8; 32];
for (i, out) in result.iter_mut().enumerate() {
*out = (nibble(bytes[i * 2])? << 4) | nibble(bytes[i * 2 + 1])?;
}
Some(result)
}
pub fn sha256_concat_ids(ids: &[Hash256]) -> Hash256 {
let mut hasher = Sha256::new();
for id in ids {
hasher.update(id);
}
hasher.finalize().into()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sha256_determinism() {
let h1 = sha256(b"hello");
let h2 = sha256(b"hello");
assert_eq!(h1, h2);
}
#[test]
fn test_hex_roundtrip() {
let h = sha256(b"test");
let hex = hex_encode(&h);
assert_eq!(hex.len(), 64);
let decoded = hex_decode(&hex).unwrap();
assert_eq!(h, decoded);
}
#[test]
fn test_sha256_utf8() {
let h1 = sha256_utf8("bellbook.request.v1");
let h2 = sha256("bellbook.request.v1".as_bytes());
assert_eq!(h1, h2);
}
}